├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── django_migration_testcase ├── __init__.py ├── base.py ├── django_migrations.py └── south_migrations.py ├── run_tests.sh ├── setup.py ├── tests ├── test_app │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_mymodel_number.py │ │ ├── 0003_mymodel_double_number.py │ │ ├── 0004_populate_mymodel_double_number.py │ │ ├── 0005_foreignmodel.py │ │ ├── 0006_mysecondmodel.py │ │ ├── 0007_auto_20170503_2321.py │ │ └── __init__.py │ ├── models.py │ ├── south_migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto__add_field_mymodel_number.py │ │ ├── 0003_auto__add_field_mymodel_double_number.py │ │ ├── 0004_populate_mymodel_double_number.py │ │ ├── 0005_auto__add_foreignmodel.py │ │ ├── 0006_auto__add_mysecondmodel.py │ │ ├── 0007_auto__add_unique_mysecondmodel_name.py │ │ └── __init__.py │ ├── tests.py │ └── views.py ├── test_project_postgresql_django │ ├── manage.py │ ├── migration_test │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── test_app │ └── test_second_app ├── test_project_postgresql_south │ ├── manage.py │ ├── migration_test │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── test_app │ └── test_second_app ├── test_project_sqlite_django │ ├── manage.py │ ├── migration_test │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── test_app │ └── test_second_app ├── test_project_sqlite_south │ ├── manage.py │ ├── migration_test │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── test_app │ └── test_second_app └── test_second_app │ ├── __init__.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0002_mymodel_number.py │ ├── 0003_mymodel_my_model.py │ └── __init__.py │ ├── models.py │ ├── south_migrations │ ├── 0001_initial.py │ ├── 0002_mymodel_number.py │ ├── 0003_auto__add_field_mymodel_my_model.py │ └── __init__.py │ ├── tests.py │ └── views.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | .venv 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | # Sqlite database 58 | *.sqlite3 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | - "3.7" 8 | install: pip install tox-travis tox flake8 9 | dist: xenial 10 | services: 11 | - postgresql 12 | sudo: false 13 | script: tox 14 | before_script: 15 | - flake8 django_migration_testcase tests --ignore=E501,E128,E402 16 | - psql -c 'create database migration_test;' -U postgres 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Nelson, Andrew Plummer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django_migration_testcase 2 | [![Build Status](https://travis-ci.org/plumdog/django_migration_testcase.svg?branch=master)](https://travis-ci.org/plumdog/django_migration_testcase) 3 | 4 | For testing migrations in Django >= 1.4 (both South and Django migrations) 5 | 6 | Because migrations are important. And if they go wrong, people get 7 | angry. How better to be sure that they won't go wrong than to run 8 | tests. 9 | 10 | I found [this article](https://micknelson.wordpress.com/2013/03/01/testing-django-migrations/) 11 | on writing tests around South migrations, which I had used, but as of 12 | Django 1.7, I was out of luck. So I wrote this. It also supports 13 | Django 1.4, 1.5 and 1.6. 14 | 15 | This project is very much in its infancy, and I'd be really interested 16 | to know how others get on. Also, if there's a better strategy or 17 | existing library, I'd love to know about it. 18 | 19 | If there's anything not made clear in this README, please open an 20 | issue. 21 | 22 | Quickstart 23 | ---------- 24 | 25 | ```python 26 | 27 | from django_migration_testcase import MigrationTest 28 | 29 | 30 | class MyMigrationTest(MigrationTest): 31 | 32 | # See below for handling multiple apps 33 | app_name = 'my_app' 34 | # Or just the numbers, if you prefer brevity. 35 | before = '0001_initial' 36 | after = '0002_change_fields' 37 | 38 | # Can have any name like test_*, is just a test method. 39 | # MigrationTest subclasses django.test.TransactionTestCase 40 | def test_migration(self): 41 | # Load some data. Don't directly import models. At this point, 42 | # the database is at self.before, and the models have fields 43 | # set accordingly. Can get models from other apps with 44 | # self.get_model_before('otherapp.OtherModel') 45 | 46 | MyModel = self.get_model_before('MyModel') 47 | 48 | # ... save some models 49 | 50 | # Trigger the migration 51 | self.run_migration() 52 | 53 | # Now run some assertions based on what the data should now 54 | # look like. The database will now be at self.after. To run 55 | # queries via the models, reload the model. 56 | 57 | MyModel = self.get_model_after('MyModel') 58 | ``` 59 | 60 | 61 | Reverse Migrations 62 | ------------------ 63 | 64 | You can test reverse migrations just like forward migrations. If you 65 | set `before = '0002'` and `after = '0001'` then when you call 66 | `self.run_migration()` in you test method it will run the reverse 67 | migration from `0002`. 68 | 69 | Alternatively, you can write a test where you run the migrations 70 | forward the backwards again. For example: 71 | 72 | ```python 73 | 74 | from django_migration_testcase import MigrationTest 75 | 76 | 77 | class MyMigrationTest(MigrationTest): 78 | app_name = 'my_app' 79 | before = '0001' 80 | after = '0002' 81 | 82 | def test_migration(self): 83 | # Set up some data... 84 | 85 | # Run the migration forwards 86 | self.run_migration() 87 | 88 | # Check that the data looks right 89 | 90 | # Run the migration back down again 91 | self.run_reverse_migration() 92 | 93 | # Check that the data has been put back as you expect. 94 | ``` 95 | 96 | Migrating Multiple Apps 97 | ----------------------- 98 | 99 | If you want to test that two apps in different apps play nicely 100 | together, you can set `self.before` and `self.after` as a list of 101 | two-tuples, each of which should be `([[app-name]], 102 | [[migration]])`. (This is done this way rather than as a dict because 103 | order may matter - migrations are run in the order they are listed.) 104 | 105 | Eg 106 | ```python 107 | class MigrateBothMigrationTest(MigrationTest): 108 | # Don't set app_name on the class, because there isn't one. 109 | before = [('test_app', '0001'), ('test_second_app', '0001')] 110 | after = [('test_app', '0002'), ('test_second_app', '0002')] 111 | ``` 112 | 113 | Then, in your `test_*` methods, when you need to get a model, you must 114 | specify the app name (if you're only testing one app, then it can look 115 | at `self.app_name`). So you can't do 116 | `self.get_model_before('MyModel')`, you have to do 117 | `self.get_model_before('test_app.MyModel')`. 118 | 119 | Migration Versions 120 | ------------------ 121 | 122 | By setting the migration version as `'zero'`, this sets the target 123 | migration as before the first migration. 124 | 125 | In cases where two migrations have the same number-prefix, you can 126 | specify the full version to resolve this. Or you can use the full 127 | version anyway in the name of explicitness. 128 | 129 | Relationships between models in different apps 130 | ---------------------------------------------- 131 | 132 | This works in with Django's migrations. But (at present) doesn't with 133 | South. There's an additional problem with South, as the metadata for a 134 | migration doesn't contain the state of all models, just those that 135 | were frozen. So how does your test work out what a model should look 136 | like for a migration, if the migration itself doesn't know? Answers in 137 | a PR please, or just any suggestions. 138 | 139 | How it works 140 | ------------ 141 | 142 | In Django's migrations, when writing data migrations, rather than 143 | directly importing models, you load them using `apps.get_model()` -- 144 | see 145 | [here](https://docs.djangoproject.com/en/1.7/topics/migrations/#data-migrations). 146 | I've tried to unravel the migrations framework to do the same thing 147 | here, so that we load models dynamically. 148 | 149 | The `migrate` and `migrate_kwargs` methods 150 | ------------------------------------------ 151 | 152 | The test method has a `migrate` method that takes an app name, a 153 | version and an optional `fake` boolean. By default, this just calls: 154 | ```python 155 | call_command('migrate', app_name, version, 156 | fake=fake, verbosity=0) 157 | ``` 158 | 159 | If you need to alter your migrate command, you can either override this method, or you might just override `migrate_kwargs`, which by default sets `verbosity=0`. Extend this to pass more options/different options. Note that if you try to set a `fake` kwarg from this method, it will be ignored. 160 | 161 | Testing migration failures 162 | -------------------------- 163 | 164 | Sometimes you want a migration to fail, and then fix the problem by hand (Ex: Resolve issues with unique together). 165 | 166 | To test this you will have to create a migration test provoking the problem. However if the data causing the migration error is not automatically cleaned up after the migration. The `tearDown`of `MigrationTest` will fail to migrate back the database in a good state and might create havoc in other tests. 167 | 168 | For django 1.7+ (with database engines other than sqlite3) the helpful `@idempotent_transaction` decorator is available to automatically revert data created during the test (on both success and failure). 169 | 170 | ```python 171 | from django_migration_testcase.base import idempotent_transaction 172 | 173 | @unittest.skipIf(django.VERSION < (1, 7), 'Not supported by older django versions') 174 | class TeardownFailCanBeAvoidedWithIdempotentTransaction(MigrationTest): 175 | before = '0006' 176 | after = '0007' 177 | 178 | app_name = 'test_app' 179 | 180 | @idempotent_transaction 181 | def test_second_model_name_is_unique(self): 182 | model_before = self.get_model_before('MySecondModel') 183 | model_before.objects.create(name='foo') 184 | model_before.objects.create(name='foo') 185 | with self.assertRaises(IntegrityError): 186 | self.run_migration() 187 | ``` 188 | 189 | For django <1.7 you will have to clean up the data by hand. 190 | 191 | For more information see issue [Issue #33](https://github.com/plumdog/django_migration_testcase/issues/33) the [Pull Request #35](https://github.com/plumdog/django_migration_testcase/pull/35). 192 | 193 | Tests 194 | ----- 195 | 196 | There's a test app that has four migrations. It is linked to different 197 | projects within the test directory, one that uses postgres, and one 198 | that uses sqlite3, and one for each but with South. To run the tests, 199 | `pip install django psycopg2 [south] -e .` then `./run_tests.sh`. Or, 200 | to cover all supported Django and Python versions: `pip install tox` 201 | then `tox`. 202 | -------------------------------------------------------------------------------- /django_migration_testcase/__init__.py: -------------------------------------------------------------------------------- 1 | import django 2 | 3 | if django.VERSION >= (1, 7): 4 | from .django_migrations import MigrationTest # noqa 5 | else: 6 | from .south_migrations import MigrationTest # noqa 7 | -------------------------------------------------------------------------------- /django_migration_testcase/base.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | import django 4 | from django.db import transaction 5 | from django.conf import settings 6 | from django.test import TransactionTestCase 7 | from django.core.management import call_command 8 | 9 | 10 | class InvalidModelStateError(Exception): 11 | pass 12 | 13 | 14 | def idempotent_transaction(func): 15 | if django.VERSION < (1, 7,) or django.VERSION >= (2, 0) and settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3': 16 | return func 17 | else: 18 | @functools.wraps(func) 19 | def func_wrapper(*args, **kwargs): 20 | with transaction.atomic(): 21 | sp = transaction.savepoint() 22 | try: 23 | func(*args, **kwargs) 24 | transaction.savepoint_rollback(sp) 25 | except BaseException: 26 | raise 27 | return func_wrapper 28 | 29 | 30 | class BaseMigrationTestCase(TransactionTestCase): 31 | __abstract__ = True 32 | 33 | before = None 34 | after = None 35 | app_name = None 36 | 37 | def __init__(self, *args, **kwargs): 38 | super(BaseMigrationTestCase, self).__init__(*args, **kwargs) 39 | # if self.app_name is None, then assume self.before is a list 40 | # of 2-tuples. This is more explicit (and easier to document). 41 | # TODO: add more sanity checks 42 | if self.app_name: 43 | self.before = [(self.app_name, self.before)] 44 | self.after = [(self.app_name, self.after)] 45 | 46 | def tearDown(self): 47 | # We do need to tidy up and take the database to its final 48 | # state so that we don't get errors when the final truncating 49 | # happens. 50 | for app_name, _ in self.after: 51 | self.migrate(app_name, version=None) 52 | super(BaseMigrationTestCase, self).tearDown() 53 | 54 | def setUp(self): 55 | self._migration_run = False 56 | 57 | def _check_migration_run(self): 58 | if not self._migration_run: 59 | raise InvalidModelStateError('Migration(s) not yet run, invalid state requested') 60 | 61 | def _check_migration_not_run(self): 62 | if self._migration_run: 63 | raise InvalidModelStateError('Migration(s) already run, invalid state requested') 64 | 65 | def get_model_before(self, model_name): 66 | raise NotImplementedError() 67 | 68 | def get_model_after(self, model_name): 69 | raise NotImplementedError() 70 | 71 | def run_migration(self): 72 | raise NotImplementedError() 73 | 74 | def run_reverse_migration(self): 75 | raise NotImplementedError() 76 | 77 | def migrate_kwargs(self): 78 | if django.VERSION >= (2, 0,): 79 | return { 80 | 'verbosity': 0, 81 | 'interactive': False, 82 | } 83 | else: 84 | return { 85 | 'verbosity': 0, 86 | 'no_initial_data': True, 87 | 'interactive': False, 88 | } 89 | 90 | def migrate(self, app_name, version, fake=False): 91 | kwargs = self.migrate_kwargs() 92 | kwargs['fake'] = fake 93 | # For Django 1.7 - does a len() check on args. 94 | if version: 95 | args = ('migrate', app_name, version) 96 | else: 97 | args = ('migrate', app_name) 98 | call_command(*args, **kwargs) 99 | 100 | def _get_app_and_model_name(self, model_name): 101 | if '.' in model_name: 102 | app_name, model_name = model_name.split('.', 2) 103 | elif self.app_name: 104 | app_name = self.app_name 105 | else: 106 | raise ValueError('Must specify app name') 107 | return app_name, model_name 108 | -------------------------------------------------------------------------------- /django_migration_testcase/django_migrations.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.db import connection 3 | from django.db.migrations.loader import MigrationLoader 4 | 5 | from .base import BaseMigrationTestCase 6 | 7 | 8 | class MigrationTest(BaseMigrationTestCase): 9 | 10 | def setUp(self): 11 | self.apps_before = None 12 | self.apps_after = None 13 | 14 | super(MigrationTest, self).setUp() 15 | for app_name, version in self.before: 16 | self.migrate(app_name, version) 17 | 18 | def _get_apps_for_migration(self, migration_states): 19 | loader = MigrationLoader(connection) 20 | full_names = [] 21 | for app_name, migration_name in migration_states: 22 | if migration_name != 'zero': 23 | migration_name = loader.get_migration_by_prefix(app_name, migration_name).name 24 | full_names.append((app_name, migration_name)) 25 | state = loader.project_state(full_names) 26 | if django.VERSION < (1, 8): 27 | state.render() 28 | return state.apps 29 | 30 | def _get_model(self, model_name, before=True): 31 | app_name, model_name = self._get_app_and_model_name(model_name) 32 | if before: 33 | if not self.apps_before: 34 | self.apps_before = self._get_apps_for_migration(self.before) 35 | return self.apps_before.get_model(app_name, model_name) 36 | else: 37 | if not self.apps_after: 38 | self.apps_after = self._get_apps_for_migration(self.after) 39 | return self.apps_after.get_model(app_name, model_name) 40 | 41 | def run_migration(self): 42 | for app_name, version in self.after: 43 | self.migrate(app_name, version) 44 | self._migration_run = True 45 | 46 | def run_reverse_migration(self): 47 | self._check_migration_run() 48 | for app_name, version in self.before: 49 | self.migrate(app_name, version) 50 | self._migration_run = False 51 | 52 | def get_model_before(self, model_name): 53 | self._check_migration_not_run() 54 | return self._get_model(model_name, before=True) 55 | 56 | def get_model_after(self, model_name): 57 | self._check_migration_run() 58 | return self._get_model(model_name, before=False) 59 | -------------------------------------------------------------------------------- /django_migration_testcase/south_migrations.py: -------------------------------------------------------------------------------- 1 | from south.migration import Migrations 2 | 3 | from .base import BaseMigrationTestCase 4 | 5 | 6 | class MigrationTest(BaseMigrationTestCase): 7 | """Test for migrations, reworked from: 8 | https://micknelson.wordpress.com/2013/03/01/testing-django-migrations/ 9 | 10 | """ 11 | 12 | def setUp(self): 13 | super(MigrationTest, self).setUp() 14 | 15 | self.before_migrations = [] 16 | for app_name, version in self.before: 17 | migrations = Migrations(app_name) 18 | if version != 'zero': 19 | full_version = migrations.guess_migration( 20 | self._get_migration_number(version)).name() 21 | else: 22 | full_version = 'zero' 23 | self.before_migrations.append((app_name, full_version)) 24 | self.after_migrations = [] 25 | for app_name, version in self.after: 26 | migrations = Migrations(app_name) 27 | 28 | if version != 'zero': 29 | full_version = migrations.guess_migration( 30 | self._get_migration_number(version)).name() 31 | else: 32 | full_version = 'zero' 33 | self.after_migrations.append((app_name, full_version)) 34 | 35 | self.before_orm = {} 36 | for app_name, version in self.before_migrations: 37 | if version != 'zero': 38 | migrations = Migrations(app_name) 39 | self.before_orm[app_name] = migrations[version].orm() 40 | self.after_orm = {} 41 | for app_name, version in self.after_migrations: 42 | if version != 'zero': 43 | migrations = Migrations(app_name) 44 | self.after_orm[app_name] = migrations[version].orm() 45 | 46 | for app_name, version in self.before_migrations: 47 | # Do a fake migration first to update the migration history. 48 | self.migrate(app_name, version=None, fake=True) 49 | self.migrate(app_name, version=version) 50 | 51 | def _get_model(self, model_name, orm_dict): 52 | app_name, model_name = self._get_app_and_model_name(model_name) 53 | # Because we store all the orms for each migration against 54 | # their app name, lookup the relevant orm state first. 55 | orm = orm_dict[app_name] 56 | model_name = '{app_name}.{model_name}'.format( 57 | app_name=app_name, 58 | model_name=model_name) 59 | return orm[model_name] 60 | 61 | def get_model_before(self, model_name): 62 | self._check_migration_not_run() 63 | return self._get_model(model_name, self.before_orm) 64 | 65 | def get_model_after(self, model_name): 66 | self._check_migration_run() 67 | return self._get_model(model_name, self.after_orm) 68 | 69 | def run_migration(self): 70 | for app_name, version in self.after_migrations: 71 | self.migrate(app_name, version) 72 | self._migration_run = True 73 | 74 | def run_reverse_migration(self): 75 | self._check_migration_run() 76 | for app_name, version in self.before: 77 | self.migrate(app_name, version) 78 | self._migration_run = False 79 | 80 | def _get_migration_number(self, migration_name): 81 | # TODO: make this better and report exception 82 | return migration_name.split('_')[0] 83 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SUCCESS=0 4 | 5 | DJANGO_VERSION=$("$VIRTUAL_ENV"/bin/django-admin.py --version) 6 | 7 | # A hack so that we run the south tests for django 1.6 and below, but 8 | # the django migration tests for 1.7 and above 9 | if [[ $DJANGO_VERSION == "1.4"* || $DJANGO_VERSION == "1.5"* || $DJANGO_VERSION == "1.6"* ]]; then 10 | NAME='test_project_*_south' 11 | else 12 | NAME='test_project_*_django' 13 | fi 14 | 15 | 16 | for f in $(find tests -type d -name "$NAME"); do 17 | echo "Tests for $f" 18 | if ! (cd "$f" && ./manage.py test test_app test_second_app) ; then 19 | SUCCESS=1; 20 | fi 21 | done 22 | 23 | 24 | exit $SUCCESS 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | 4 | setup( 5 | name='django-migration-testcase', 6 | version='0.0.15', 7 | author='Andrew Plummer', 8 | author_email='plummer574@gmail.com', 9 | description='For testing migrations in Django', 10 | url='https://github.com/plumdog/django_migration_testcase', 11 | packages=find_packages(), 12 | install_requires=['Django>=1.4'], 13 | classifiers=[ 14 | 'Programming Language :: Python', 15 | 'Programming Language :: Python :: 2.7', 16 | 'Programming Language :: Python :: 3', 17 | 'Programming Language :: Python :: 3.4', 18 | 'Programming Language :: Python :: 3.5', 19 | 'Programming Language :: Python :: 3.6', 20 | 'Programming Language :: Python :: 3.7', 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /tests/test_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_app/__init__.py -------------------------------------------------------------------------------- /tests/test_app/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='MyModel', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('name', models.CharField(max_length=100)), 18 | ], 19 | options={ 20 | }, 21 | bases=(models.Model,), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /tests/test_app/migrations/0002_mymodel_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | def forwards(apps, schema_editor): 8 | pass 9 | # print(repr(apps)) 10 | # print(repr(schema_editor)) 11 | 12 | 13 | class Migration(migrations.Migration): 14 | 15 | dependencies = [ 16 | ('test_app', '0001_initial'), 17 | ] 18 | 19 | operations = [ 20 | migrations.AddField( 21 | model_name='mymodel', 22 | name='number', 23 | field=models.IntegerField(null=True), 24 | preserve_default=True, 25 | ), 26 | migrations.RunPython(forwards, lambda apps, schema_editor: None) 27 | ] 28 | -------------------------------------------------------------------------------- /tests/test_app/migrations/0003_mymodel_double_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('test_app', '0002_mymodel_number'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='mymodel', 16 | name='double_number', 17 | field=models.IntegerField(null=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /tests/test_app/migrations/0004_populate_mymodel_double_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | def forwards(apps, schema_editor): 8 | MyModel = apps.get_model("test_app", "MyModel") 9 | MyModel.objects.update(double_number=models.F('number') * 2) 10 | 11 | 12 | def backwards(apps, schema_editor): 13 | pass 14 | 15 | 16 | class Migration(migrations.Migration): 17 | 18 | dependencies = [ 19 | ('test_app', '0003_mymodel_double_number'), 20 | ] 21 | 22 | operations = [ 23 | migrations.RunPython(forwards, backwards) 24 | ] 25 | -------------------------------------------------------------------------------- /tests/test_app/migrations/0005_foreignmodel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('test_app', '0004_populate_mymodel_double_number'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ForeignModel', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('name', models.CharField(max_length=100)), 19 | ('my', models.ForeignKey(to='test_app.MyModel', on_delete=models.CASCADE)), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /tests/test_app/migrations/0006_mysecondmodel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11 on 2017-05-03 23:21 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('test_app', '0005_foreignmodel'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='MySecondModel', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=100)), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /tests/test_app/migrations/0007_auto_20170503_2321.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11 on 2017-05-03 23:21 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('test_app', '0006_mysecondmodel'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='mysecondmodel', 17 | name='name', 18 | field=models.CharField(max_length=100, unique=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /tests/test_app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_app/migrations/__init__.py -------------------------------------------------------------------------------- /tests/test_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class MyModel(models.Model): 5 | name = models.CharField(max_length=100) 6 | number = models.IntegerField(null=True) 7 | double_number = models.IntegerField(null=True) 8 | 9 | 10 | class ForeignModel(models.Model): 11 | name = models.CharField(max_length=100) 12 | my = models.ForeignKey(MyModel, on_delete=models.CASCADE) 13 | 14 | 15 | class MySecondModel(models.Model): 16 | name = models.CharField(max_length=100, unique=True) 17 | -------------------------------------------------------------------------------- /tests/test_app/south_migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding model 'MyModel' 10 | db.create_table(u'test_app_mymodel', ( 11 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 12 | ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), 13 | )) 14 | db.send_create_signal(u'test_app', ['MyModel']) 15 | 16 | def backwards(self, orm): 17 | # Deleting model 'MyModel' 18 | db.delete_table(u'test_app_mymodel') 19 | 20 | models = { 21 | u'test_app.mymodel': { 22 | 'Meta': {'object_name': 'MyModel'}, 23 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 24 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 25 | } 26 | } 27 | 28 | complete_apps = ['test_app'] 29 | -------------------------------------------------------------------------------- /tests/test_app/south_migrations/0002_auto__add_field_mymodel_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding field 'MyModel.number' 10 | db.add_column(u'test_app_mymodel', 'number', 11 | self.gf('django.db.models.fields.IntegerField')(null=True), 12 | keep_default=False) 13 | 14 | def backwards(self, orm): 15 | # Deleting field 'MyModel.number' 16 | db.delete_column(u'test_app_mymodel', 'number') 17 | 18 | models = { 19 | u'test_app.mymodel': { 20 | 'Meta': {'object_name': 'MyModel'}, 21 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 22 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 23 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 24 | } 25 | } 26 | 27 | complete_apps = ['test_app'] 28 | -------------------------------------------------------------------------------- /tests/test_app/south_migrations/0003_auto__add_field_mymodel_double_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding field 'MyModel.double_number' 10 | db.add_column(u'test_app_mymodel', 'double_number', 11 | self.gf('django.db.models.fields.IntegerField')(null=True), 12 | keep_default=False) 13 | 14 | def backwards(self, orm): 15 | # Deleting field 'MyModel.double_number' 16 | db.delete_column(u'test_app_mymodel', 'double_number') 17 | 18 | models = { 19 | u'test_app.mymodel': { 20 | 'Meta': {'object_name': 'MyModel'}, 21 | 'double_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 22 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 23 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 24 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 25 | } 26 | } 27 | 28 | complete_apps = ['test_app'] 29 | -------------------------------------------------------------------------------- /tests/test_app/south_migrations/0004_populate_mymodel_double_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.v2 import DataMigration 3 | from django.db import models 4 | 5 | 6 | class Migration(DataMigration): 7 | 8 | def forwards(self, orm): 9 | MyModel = orm.MyModel 10 | MyModel.objects.update(double_number=models.F('number') * 2) 11 | 12 | def backwards(self, orm): 13 | "Write your backwards methods here." 14 | 15 | models = { 16 | u'test_app.mymodel': { 17 | 'Meta': {'object_name': 'MyModel'}, 18 | 'double_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 19 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 20 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 21 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 22 | } 23 | } 24 | 25 | complete_apps = ['test_app'] 26 | symmetrical = True 27 | -------------------------------------------------------------------------------- /tests/test_app/south_migrations/0005_auto__add_foreignmodel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding model 'ForeignModel' 10 | db.create_table(u'test_app_foreignmodel', ( 11 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 12 | ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), 13 | ('my', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['test_app.MyModel'])), 14 | )) 15 | db.send_create_signal(u'test_app', ['ForeignModel']) 16 | 17 | def backwards(self, orm): 18 | # Deleting model 'ForeignModel' 19 | db.delete_table(u'test_app_foreignmodel') 20 | 21 | models = { 22 | u'test_app.foreignmodel': { 23 | 'Meta': {'object_name': 'ForeignModel'}, 24 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 25 | 'my': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['test_app.MyModel']"}), 26 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 27 | }, 28 | u'test_app.mymodel': { 29 | 'Meta': {'object_name': 'MyModel'}, 30 | 'double_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 31 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 32 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 33 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 34 | } 35 | } 36 | 37 | complete_apps = ['test_app'] 38 | -------------------------------------------------------------------------------- /tests/test_app/south_migrations/0006_auto__add_mysecondmodel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding model 'MySecondModel' 10 | db.create_table('test_app_mysecondmodel', ( 11 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 12 | ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), 13 | )) 14 | db.send_create_signal('test_app', ['MySecondModel']) 15 | 16 | def backwards(self, orm): 17 | # Deleting model 'MySecondModel' 18 | db.delete_table('test_app_mysecondmodel') 19 | 20 | models = { 21 | 'test_app.foreignmodel': { 22 | 'Meta': {'object_name': 'ForeignModel'}, 23 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 24 | 'my': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['test_app.MyModel']"}), 25 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 26 | }, 27 | 'test_app.mymodel': { 28 | 'Meta': {'object_name': 'MyModel'}, 29 | 'double_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 30 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 31 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 32 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 33 | }, 34 | 'test_app.mysecondmodel': { 35 | 'Meta': {'object_name': 'MySecondModel'}, 36 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 38 | } 39 | } 40 | 41 | complete_apps = ['test_app'] 42 | -------------------------------------------------------------------------------- /tests/test_app/south_migrations/0007_auto__add_unique_mysecondmodel_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding unique constraint on 'MySecondModel', fields ['name'] 10 | db.create_unique('test_app_mysecondmodel', ['name']) 11 | 12 | def backwards(self, orm): 13 | # Removing unique constraint on 'MySecondModel', fields ['name'] 14 | db.delete_unique('test_app_mysecondmodel', ['name']) 15 | 16 | models = { 17 | 'test_app.foreignmodel': { 18 | 'Meta': {'object_name': 'ForeignModel'}, 19 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 20 | 'my': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['test_app.MyModel']"}), 21 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 22 | }, 23 | 'test_app.mymodel': { 24 | 'Meta': {'object_name': 'MyModel'}, 25 | 'double_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 26 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 28 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 29 | }, 30 | 'test_app.mysecondmodel': { 31 | 'Meta': {'object_name': 'MySecondModel'}, 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 33 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}) 34 | } 35 | } 36 | 37 | complete_apps = ['test_app'] 38 | -------------------------------------------------------------------------------- /tests/test_app/south_migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_app/south_migrations/__init__.py -------------------------------------------------------------------------------- /tests/test_app/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import django 4 | from django.db import IntegrityError 5 | from django.conf import settings 6 | 7 | from django_migration_testcase import MigrationTest 8 | from django_migration_testcase.base import InvalidModelStateError, idempotent_transaction 9 | 10 | 11 | class ExampleMigrationTest(MigrationTest): 12 | before = '0001_initial' 13 | after = '0002_mymodel_number' 14 | app_name = 'test_app' 15 | 16 | def test_migration(self): 17 | MyModel = self.get_model_before('MyModel') 18 | 19 | for i in range(10): 20 | mymodel = MyModel() 21 | mymodel.name = 'example name {}'.format(i) 22 | mymodel.save() 23 | self.assertEqual(MyModel.objects.count(), 10) 24 | 25 | self.run_migration() 26 | 27 | MyModel = self.get_model_after('MyModel') 28 | self.assertEqual(MyModel.objects.count(), 10) 29 | 30 | def test_run_reverse_migration(self): 31 | MyModel = self.get_model_before('MyModel') 32 | 33 | for i in range(10): 34 | mymodel = MyModel() 35 | mymodel.name = 'example name {}'.format(i) 36 | mymodel.save() 37 | self.assertEqual(MyModel.objects.count(), 10) 38 | 39 | self.run_migration() 40 | 41 | MyModel = self.get_model_after('MyModel') 42 | self.assertEqual(MyModel.objects.count(), 10) 43 | 44 | self.run_reverse_migration() 45 | 46 | self.assertEqual(MyModel.objects.count(), 10) 47 | 48 | def test_invalid_field(self): 49 | MyModel = self.get_model_before('MyModel') 50 | mymodel = MyModel() 51 | mymodel.number = 10 52 | mymodel.save() 53 | 54 | mymodel = MyModel.objects.get() 55 | with self.assertRaises(AttributeError): 56 | mymodel.number 57 | 58 | self.run_migration() 59 | 60 | MyModel = self.get_model_after('MyModel') 61 | mymodel = MyModel.objects.get() 62 | self.assertEqual(mymodel.number, None) 63 | 64 | mymodel.number = 10 65 | mymodel.save() 66 | 67 | mymodel = MyModel.objects.get() 68 | self.assertEqual(mymodel.number, 10) 69 | 70 | 71 | def field_names(model_class): 72 | try: 73 | return model_class._meta.get_all_field_names() 74 | except AttributeError: 75 | return [f.name for f in model_class._meta.get_fields()] 76 | 77 | 78 | class AddDoubleNumberTest(MigrationTest): 79 | before = '0002_mymodel_number' 80 | after = '0003_mymodel_double_number' 81 | app_name = 'test_app' 82 | 83 | def test_migration(self): 84 | MyModel = self.get_model_before('MyModel') 85 | self.assertNotIn('double_number', field_names(MyModel)) 86 | 87 | self.run_migration() 88 | 89 | MyModel = self.get_model_after('MyModel') 90 | self.assertIn('double_number', field_names(MyModel)) 91 | 92 | 93 | class MigrationsByNumberOnlyTest(MigrationTest): 94 | before = '0002' 95 | after = '0003' 96 | app_name = 'test_app' 97 | 98 | def test_migration(self): 99 | MyModel = self.get_model_before('MyModel') 100 | self.assertNotIn('double_number', field_names(MyModel)) 101 | 102 | self.run_migration() 103 | 104 | MyModel = self.get_model_after('MyModel') 105 | self.assertIn('double_number', field_names(MyModel)) 106 | 107 | 108 | class PopulateDoubleNumberTest(MigrationTest): 109 | before = '0003_mymodel_double_number' 110 | after = '0004_populate_mymodel_double_number' 111 | app_name = 'test_app' 112 | 113 | def test_migration(self): 114 | MyModel = self.get_model_before('MyModel') 115 | 116 | for i in range(10): 117 | mymodel = MyModel() 118 | mymodel.name = 'example name {}'.format(i) 119 | mymodel.number = i 120 | mymodel.save() 121 | 122 | self.run_migration() 123 | 124 | MyModel = self.get_model_after('MyModel') 125 | for mymodel in MyModel.objects.all(): 126 | self.assertEqual(mymodel.number * 2, mymodel.double_number) 127 | 128 | 129 | class GetModelMigrationTest(MigrationTest): 130 | before = '0001_initial' 131 | after = '0002_mymodel_number' 132 | app_name = 'test_app' 133 | 134 | def test_migration(self): 135 | MyModel = self.get_model_before('test_app.MyModel') 136 | self.assertEqual(MyModel.__name__, 'MyModel') 137 | 138 | self.run_migration() 139 | 140 | MyModel = self.get_model_after('test_app.MyModel') 141 | self.assertEqual(MyModel.__name__, 'MyModel') 142 | 143 | 144 | class ForeignKeyTest(MigrationTest): 145 | before = '0004_populate_mymodel_double_number' 146 | after = '0005_foreignmodel' 147 | app_name = 'test_app' 148 | 149 | def test_migration(self): 150 | MyModel = self.get_model_before('test_app.MyModel') 151 | self.assertEqual(MyModel.__name__, 'MyModel') 152 | 153 | self.run_migration() 154 | 155 | ForeignModel = self.get_model_after('test_app.ForeignModel') 156 | self.assertEqual(ForeignModel.__name__, 'ForeignModel') 157 | 158 | MyModel = self.get_model_after('test_app.MyModel') 159 | self.assertEqual(MyModel.__name__, 'MyModel') 160 | 161 | my = MyModel(name='test_my', number=1, double_number=3.14) 162 | my.save() 163 | 164 | ForeignModel(name='test_foreign', my=my) 165 | 166 | def test_migration2(self): 167 | """Same test as test_migration, but this one passes.""" 168 | MyModel = self.get_model_before('test_app.MyModel') 169 | self.assertEqual(MyModel.__name__, 'MyModel') 170 | 171 | self.run_migration() 172 | 173 | ForeignModel = self.get_model_after('test_app.ForeignModel') 174 | self.assertEqual(ForeignModel.__name__, 'ForeignModel') 175 | 176 | # get_model_before/get_model_after seems to not get the same model as 177 | # this crazy thing. 178 | if django.VERSION >= (2, 0): 179 | MyModel = ForeignModel.my.field.related_model 180 | else: 181 | MyModel = ForeignModel.my.field.rel.to 182 | self.assertEqual(MyModel.__name__, 'MyModel') 183 | 184 | my = MyModel(name='test_my', number=1, double_number=3.14) 185 | my.save() 186 | 187 | ForeignModel(name='test_foreign', my=my) 188 | 189 | def test_migration_clearly(self): 190 | """A clear illustration of the problem.""" 191 | self.run_migration() 192 | 193 | ForeignModel = self.get_model_after('test_app.ForeignModel') 194 | 195 | # get_model_before/get_model_after seems to not get the same model as 196 | # this crazy thing. 197 | if django.VERSION >= (2, 0): 198 | MyModel = ForeignModel.my.field.related_model 199 | else: 200 | MyModel = ForeignModel.my.field.rel.to 201 | MyModel2 = self.get_model_after('test_app.MyModel') 202 | 203 | self.assertEqual(MyModel, MyModel2) 204 | 205 | 206 | class UtilsMigrationTest(MigrationTest): 207 | before = '0001_initial' 208 | after = '0002_mymodel_number' 209 | app_name = 'test_app' 210 | 211 | def test_migration_not_run_exception(self): 212 | with self.assertRaises(InvalidModelStateError): 213 | self.get_model_after('MyModel') 214 | with self.assertRaises(InvalidModelStateError): 215 | self.run_reverse_migration() 216 | 217 | def test_migration_already_run_exception(self): 218 | self.run_migration() 219 | with self.assertRaises(InvalidModelStateError): 220 | self.get_model_before('MyModel') 221 | 222 | 223 | class MigrateFromZero(MigrationTest): 224 | before = 'zero' 225 | after = '0001_initial' 226 | 227 | app_name = 'test_app' 228 | 229 | def test_model_exists(self): 230 | 231 | with self.assertRaises(LookupError): 232 | self.get_model_before('MyModel') 233 | 234 | self.run_migration() 235 | 236 | MyModel = self.get_model_after('MyModel') 237 | self.assertEqual(MyModel.__name__, 'MyModel') 238 | 239 | 240 | class TeardownCanFail(MigrationTest): 241 | before = '0006' 242 | after = '0007' 243 | 244 | app_name = 'test_app' 245 | 246 | def test_second_model_name_is_unique(self): 247 | model_before = self.get_model_before('MySecondModel') 248 | model_before.objects.create(name='foo') 249 | model_before.objects.create(name='foo') 250 | with self.assertRaises(IntegrityError): 251 | self.run_migration() 252 | 253 | def tearDown(self): 254 | self.assertTrue(self.get_model_before('MySecondModel').objects.all().exists()) 255 | with self.assertRaises(IntegrityError): 256 | # tearDown fails since migrations runs again with the data 257 | super(TeardownCanFail, self).tearDown() 258 | 259 | self.get_model_before('MySecondModel').objects.all().delete() 260 | super(TeardownCanFail, self).tearDown() 261 | 262 | 263 | @unittest.skipIf(django.VERSION < (1, 7), 'Not supported by older django versions') 264 | @unittest.skipIf(django.VERSION >= (2, 0) and settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3', 265 | 'Not supported with django2 with sqlite3') 266 | class TeardownFailCanBeAvoidedWithIdempotentTransaction(MigrationTest): 267 | before = '0006' 268 | after = '0007' 269 | 270 | app_name = 'test_app' 271 | 272 | @idempotent_transaction 273 | def test_second_model_name_is_unique(self): 274 | model_before = self.get_model_before('MySecondModel') 275 | model_before.objects.create(name='foo') 276 | model_before.objects.create(name='foo') 277 | with self.assertRaises(IntegrityError): 278 | self.run_migration() 279 | -------------------------------------------------------------------------------- /tests/test_app/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_app/views.py -------------------------------------------------------------------------------- /tests/test_project_postgresql_django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "migration_test.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /tests/test_project_postgresql_django/migration_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_project_postgresql_django/migration_test/__init__.py -------------------------------------------------------------------------------- /tests/test_project_postgresql_django/migration_test/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for migration_test project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.7/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.7/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | import django 14 | 15 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 16 | 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = 'ojiq%@3mm3_2u#5m+k-d_$)26i#7_lw4em(ajb&^(^(fr#577s' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = True 26 | 27 | TEMPLATE_DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = ( 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 41 | 'test_app', 42 | 'test_second_app', 43 | ) 44 | 45 | MIDDLEWARE = ( 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ) 54 | 55 | if django.VERSION < (1, 10): 56 | MIDDLEWARE_CLASSES = MIDDLEWARE 57 | 58 | ROOT_URLCONF = 'migration_test.urls' 59 | 60 | WSGI_APPLICATION = 'migration_test.wsgi.application' 61 | 62 | 63 | # Database 64 | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases 65 | 66 | if bool(os.environ.get('TRAVIS', False)): 67 | DBUSER = 'postgres' 68 | else: 69 | DBUSER = os.environ['USER'] 70 | DATABASES = { 71 | 'default': { 72 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 73 | 'NAME': 'migration_test', 74 | 'USER': DBUSER 75 | } 76 | } 77 | 78 | # Internationalization 79 | # https://docs.djangoproject.com/en/1.7/topics/i18n/ 80 | 81 | LANGUAGE_CODE = 'en-us' 82 | 83 | TIME_ZONE = 'UTC' 84 | 85 | USE_I18N = True 86 | 87 | USE_L10N = True 88 | 89 | USE_TZ = True 90 | 91 | 92 | # Static files (CSS, JavaScript, Images) 93 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ 94 | 95 | STATIC_URL = '/static/' 96 | -------------------------------------------------------------------------------- /tests/test_project_postgresql_django/migration_test/urls.py: -------------------------------------------------------------------------------- 1 | urlpatterns = [] 2 | -------------------------------------------------------------------------------- /tests/test_project_postgresql_django/migration_test/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for migration_test project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "migration_test.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /tests/test_project_postgresql_django/test_app: -------------------------------------------------------------------------------- 1 | ../test_app -------------------------------------------------------------------------------- /tests/test_project_postgresql_django/test_second_app: -------------------------------------------------------------------------------- 1 | ../test_second_app -------------------------------------------------------------------------------- /tests/test_project_postgresql_south/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "migration_test.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /tests/test_project_postgresql_south/migration_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_project_postgresql_south/migration_test/__init__.py -------------------------------------------------------------------------------- /tests/test_project_postgresql_south/migration_test/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for migration_test project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = 'fs32tw4#@wbf9zqf0w1!k-kw%s519x=iu@x9$xe3+yq&3_v-99' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'south', 40 | 41 | 'test_app', 42 | 'test_second_app', 43 | ) 44 | 45 | MIDDLEWARE_CLASSES = ( 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ) 53 | 54 | ROOT_URLCONF = 'migration_test.urls' 55 | 56 | WSGI_APPLICATION = 'migration_test.wsgi.application' 57 | 58 | 59 | # Database 60 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 61 | 62 | if bool(os.environ.get('TRAVIS', False)): 63 | DBUSER = 'postgres' 64 | else: 65 | DBUSER = os.environ['USER'] 66 | DATABASES = { 67 | 'default': { 68 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 69 | 'NAME': 'migration_test', 70 | 'USER': DBUSER 71 | } 72 | } 73 | 74 | # Internationalization 75 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 76 | 77 | LANGUAGE_CODE = 'en-us' 78 | 79 | TIME_ZONE = 'UTC' 80 | 81 | USE_I18N = True 82 | 83 | USE_L10N = True 84 | 85 | USE_TZ = True 86 | 87 | 88 | # Static files (CSS, JavaScript, Images) 89 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 90 | 91 | STATIC_URL = '/static/' 92 | -------------------------------------------------------------------------------- /tests/test_project_postgresql_south/migration_test/urls.py: -------------------------------------------------------------------------------- 1 | urlpatterns = [] 2 | -------------------------------------------------------------------------------- /tests/test_project_postgresql_south/migration_test/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for migration_test project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "migration_test.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /tests/test_project_postgresql_south/test_app: -------------------------------------------------------------------------------- 1 | ../test_app -------------------------------------------------------------------------------- /tests/test_project_postgresql_south/test_second_app: -------------------------------------------------------------------------------- 1 | ../test_second_app -------------------------------------------------------------------------------- /tests/test_project_sqlite_django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "migration_test.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /tests/test_project_sqlite_django/migration_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_project_sqlite_django/migration_test/__init__.py -------------------------------------------------------------------------------- /tests/test_project_sqlite_django/migration_test/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for migration_test project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.7/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.7/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | import django 14 | 15 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 16 | 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = 'ojiq%@3mm3_2u#5m+k-d_$)26i#7_lw4em(ajb&^(^(fr#577s' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = True 26 | 27 | TEMPLATE_DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = ( 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 41 | 'test_app', 42 | 'test_second_app', 43 | ) 44 | 45 | MIDDLEWARE = ( 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ) 54 | 55 | 56 | if django.VERSION < (1, 10): 57 | MIDDLEWARE_CLASSES = MIDDLEWARE 58 | 59 | ROOT_URLCONF = 'migration_test.urls' 60 | 61 | WSGI_APPLICATION = 'migration_test.wsgi.application' 62 | 63 | 64 | # Database 65 | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases 66 | 67 | DATABASES = { 68 | 'default': { 69 | 'ENGINE': 'django.db.backends.sqlite3', 70 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 71 | } 72 | } 73 | 74 | # Internationalization 75 | # https://docs.djangoproject.com/en/1.7/topics/i18n/ 76 | 77 | LANGUAGE_CODE = 'en-us' 78 | 79 | TIME_ZONE = 'UTC' 80 | 81 | USE_I18N = True 82 | 83 | USE_L10N = True 84 | 85 | USE_TZ = True 86 | 87 | 88 | # Static files (CSS, JavaScript, Images) 89 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ 90 | 91 | STATIC_URL = '/static/' 92 | -------------------------------------------------------------------------------- /tests/test_project_sqlite_django/migration_test/urls.py: -------------------------------------------------------------------------------- 1 | urlpatterns = [] 2 | -------------------------------------------------------------------------------- /tests/test_project_sqlite_django/migration_test/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for migration_test project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "migration_test.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /tests/test_project_sqlite_django/test_app: -------------------------------------------------------------------------------- 1 | ../test_app -------------------------------------------------------------------------------- /tests/test_project_sqlite_django/test_second_app: -------------------------------------------------------------------------------- 1 | ../test_second_app -------------------------------------------------------------------------------- /tests/test_project_sqlite_south/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "migration_test.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /tests/test_project_sqlite_south/migration_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_project_sqlite_south/migration_test/__init__.py -------------------------------------------------------------------------------- /tests/test_project_sqlite_south/migration_test/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for migration_test project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = 'fs32tw4#@wbf9zqf0w1!k-kw%s519x=iu@x9$xe3+yq&3_v-99' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 'south', 40 | 41 | 'test_app', 42 | 'test_second_app', 43 | ) 44 | 45 | MIDDLEWARE_CLASSES = ( 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ) 53 | 54 | ROOT_URLCONF = 'migration_test.urls' 55 | 56 | WSGI_APPLICATION = 'migration_test.wsgi.application' 57 | 58 | 59 | # Database 60 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 61 | 62 | DATABASES = { 63 | 'default': { 64 | 'ENGINE': 'django.db.backends.sqlite3', 65 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 66 | } 67 | } 68 | 69 | # Internationalization 70 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 71 | 72 | LANGUAGE_CODE = 'en-us' 73 | 74 | TIME_ZONE = 'UTC' 75 | 76 | USE_I18N = True 77 | 78 | USE_L10N = True 79 | 80 | USE_TZ = True 81 | 82 | 83 | # Static files (CSS, JavaScript, Images) 84 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 85 | 86 | STATIC_URL = '/static/' 87 | -------------------------------------------------------------------------------- /tests/test_project_sqlite_south/migration_test/urls.py: -------------------------------------------------------------------------------- 1 | urlpatterns = [] 2 | -------------------------------------------------------------------------------- /tests/test_project_sqlite_south/migration_test/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for migration_test project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "migration_test.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /tests/test_project_sqlite_south/test_app: -------------------------------------------------------------------------------- 1 | ../test_app -------------------------------------------------------------------------------- /tests/test_project_sqlite_south/test_second_app: -------------------------------------------------------------------------------- 1 | ../test_second_app -------------------------------------------------------------------------------- /tests/test_second_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_second_app/__init__.py -------------------------------------------------------------------------------- /tests/test_second_app/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='MyModel', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('name', models.CharField(max_length=100)), 18 | ], 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /tests/test_second_app/migrations/0002_mymodel_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('test_second_app', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='mymodel', 16 | name='number', 17 | field=models.IntegerField(null=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /tests/test_second_app/migrations/0003_mymodel_my_model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('test_app', '0005_foreignmodel'), 11 | ('test_second_app', '0002_mymodel_number'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='mymodel', 17 | name='my_model', 18 | field=models.ForeignKey(blank=True, to='test_app.MyModel', null=True, on_delete=models.CASCADE), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /tests/test_second_app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_second_app/migrations/__init__.py -------------------------------------------------------------------------------- /tests/test_second_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class MyModel(models.Model): 5 | name = models.CharField(max_length=100) 6 | number = models.IntegerField(null=True) 7 | my_model = models.ForeignKey( 8 | 'test_app.MyModel', blank=True, null=True, on_delete=models.CASCADE) 9 | -------------------------------------------------------------------------------- /tests/test_second_app/south_migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding model 'MyModel' 10 | db.create_table(u'test_second_app_mymodel', ( 11 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 12 | ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), 13 | )) 14 | db.send_create_signal(u'test_second_app', ['MyModel']) 15 | 16 | def backwards(self, orm): 17 | # Deleting model 'MyModel' 18 | db.delete_table(u'test_second_app_mymodel') 19 | 20 | models = { 21 | u'test_second_app.mymodel': { 22 | 'Meta': {'object_name': 'MyModel'}, 23 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 24 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 25 | } 26 | } 27 | 28 | complete_apps = ['test_second_app'] 29 | -------------------------------------------------------------------------------- /tests/test_second_app/south_migrations/0002_mymodel_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding field 'MyModel.number' 10 | db.add_column(u'test_second_app_mymodel', 'number', 11 | self.gf('django.db.models.fields.IntegerField')(null=True), 12 | keep_default=False) 13 | 14 | def backwards(self, orm): 15 | # Deleting field 'MyModel.number' 16 | db.delete_column(u'test_second_app_mymodel', 'number') 17 | 18 | models = { 19 | u'test_second_app.mymodel': { 20 | 'Meta': {'object_name': 'MyModel'}, 21 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 22 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 23 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 24 | } 25 | } 26 | 27 | complete_apps = ['test_second_app'] 28 | -------------------------------------------------------------------------------- /tests/test_second_app/south_migrations/0003_auto__add_field_mymodel_my_model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.db import db 3 | from south.v2 import SchemaMigration 4 | 5 | 6 | class Migration(SchemaMigration): 7 | 8 | def forwards(self, orm): 9 | # Adding field 'MyModel.my_model' 10 | db.add_column(u'test_second_app_mymodel', 'my_model', 11 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['test_app.MyModel'], null=True, blank=True), 12 | keep_default=False) 13 | 14 | def backwards(self, orm): 15 | # Deleting field 'MyModel.foreign' 16 | db.delete_column(u'test_second_app_mymodel', 'my_model_id') 17 | 18 | models = { 19 | u'test_app.foreignmodel': { 20 | 'Meta': {'object_name': 'ForeignModel'}, 21 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 22 | 'my': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['test_app.MyModel']"}), 23 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 24 | }, 25 | u'test_app.mymodel': { 26 | 'Meta': {'object_name': 'MyModel'}, 27 | 'double_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 28 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 30 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 31 | }, 32 | u'test_second_app.mymodel': { 33 | 'Meta': {'object_name': 'MyModel'}, 34 | 'my_model': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['test_app.MyModel']", 'null': 'True', 'blank': 'True'}), 35 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 37 | 'number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}) 38 | } 39 | } 40 | 41 | complete_apps = ['test_second_app'] 42 | -------------------------------------------------------------------------------- /tests/test_second_app/south_migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_second_app/south_migrations/__init__.py -------------------------------------------------------------------------------- /tests/test_second_app/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import django 3 | from django_migration_testcase import MigrationTest 4 | 5 | 6 | class SecondAppMigrationTest(MigrationTest): 7 | before = '0001_initial' 8 | after = '0002_mymodel_number' 9 | app_name = 'test_second_app' 10 | 11 | def test_migration(self): 12 | MyModel = self.get_model_before('MyModel') 13 | 14 | for i in range(10): 15 | mymodel = MyModel() 16 | mymodel.name = 'example name {}'.format(i) 17 | mymodel.save() 18 | self.assertEqual(MyModel.objects.count(), 10) 19 | 20 | self.run_migration() 21 | 22 | MyModel = self.get_model_after('MyModel') 23 | self.assertEqual(MyModel.objects.count(), 10) 24 | 25 | 26 | class MigrateBothMigrationTest(MigrationTest): 27 | before = [('test_app', '0001'), ('test_second_app', '0001')] 28 | after = [('test_app', '0002'), ('test_second_app', '0002')] 29 | 30 | def test_both_migrated(self): 31 | # Check test_app.MyModel hasn't been migrated 32 | test_app_MyModel = self.get_model_before('test_app.MyModel') 33 | mymodel = test_app_MyModel() 34 | mymodel.number = 10 35 | mymodel.save() 36 | 37 | mymodel = test_app_MyModel.objects.get() 38 | with self.assertRaises(AttributeError): 39 | mymodel.number 40 | 41 | # Check test_second_app.MyModel hasn't been migrated 42 | test_second_app_MyModel = self.get_model_before('test_second_app.MyModel') 43 | mymodel = test_second_app_MyModel() 44 | mymodel.number = 10 45 | mymodel.save() 46 | 47 | mymodel = test_second_app_MyModel.objects.get() 48 | with self.assertRaises(AttributeError): 49 | mymodel.number 50 | 51 | self.run_migration() 52 | 53 | # Check test_app.MyModel has been migrated 54 | test_app_MyModel = self.get_model_after('test_app.MyModel') 55 | mymodel = test_app_MyModel.objects.get() 56 | self.assertEqual(mymodel.number, None) 57 | 58 | mymodel.number = 10 59 | mymodel.save() 60 | 61 | mymodel = test_app_MyModel.objects.get() 62 | self.assertEqual(mymodel.number, 10) 63 | 64 | # Check test_second_app.MyModel has been migrated 65 | test_second_app_MyModel = self.get_model_after('test_second_app.MyModel') 66 | mymodel = test_second_app_MyModel.objects.get() 67 | self.assertEqual(mymodel.number, None) 68 | 69 | mymodel.number = 10 70 | mymodel.save() 71 | 72 | mymodel = test_second_app_MyModel.objects.get() 73 | self.assertEqual(mymodel.number, 10) 74 | 75 | 76 | # TODO: conclude if this is permanent, or if there is a workaround. 77 | @unittest.skipIf(django.VERSION < (1, 7), 'Not supported by South') 78 | class SecondAppFKToTestAppMigrationTest(MigrationTest): 79 | """We don't actually migrate anything here, before and after are the 80 | same. We just check that the model we load can be linked and 81 | saved, even if they come from different apps. 82 | 83 | """ 84 | 85 | app_name = 'test_second_app' 86 | before = '0003' 87 | after = '0003' 88 | 89 | def test_save_and_reload_model(self): 90 | MyModelSecond = self.get_model_before('test_second_app.MyModel') 91 | MyModelFirst = self.get_model_before('test_app.MyModel') 92 | 93 | mymodelfirst = MyModelFirst() 94 | mymodelfirst.save() 95 | 96 | mymodelsecond = MyModelSecond() 97 | mymodelsecond.my_model = mymodelfirst 98 | mymodelsecond.save() 99 | 100 | 101 | class MigrateFromZero(MigrationTest): 102 | before = [('test_app', '0002'), ('test_second_app', 'zero')] 103 | after = [('test_app', '0002'), ('test_second_app', '0001')] 104 | 105 | def test_model_exists(self): 106 | # Should fail because we have migrated this app back to zero. 107 | with self.assertRaises(LookupError): 108 | self.get_model_before('test_second_app.MyModel') 109 | 110 | self.run_migration() 111 | 112 | # But after the migration, it should succeed. 113 | MyModel = self.get_model_after('test_second_app.MyModel') 114 | self.assertEqual(MyModel.__name__, 'MyModel') 115 | -------------------------------------------------------------------------------- /tests/test_second_app/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plumdog/django_migration_testcase/8fb1cd9915bd028cc9fc4ad044070d8379d9c5b1/tests/test_second_app/views.py -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | # Test against latest supported version of each of python 2 and 3 for 3 | # each Django version. 4 | # 5 | # Also, make sure that all python versions used here are included in .travis.yml 6 | envlist = 7 | py27-django1{4,5}, 8 | py{27,34}-django16, 9 | py{27,34}-django17, 10 | py{27,35}-django1{8,9,10}, 11 | py{27,34,35,36,37}-django111, 12 | py{34,35,36,37}-django20, 13 | py{35,36,37}-django2{0,1,2}, 14 | 15 | [testenv] 16 | passenv = USER 17 | setenv = 18 | PYTHONPATH={toxinidir} 19 | deps = 20 | django14: Django==1.4 21 | django15: Django==1.5 22 | django16: Django==1.6 23 | django1{4,5,6}: south 24 | django17: Django==1.7 25 | django18: Django==1.8 26 | django19: Django==1.9 27 | django110: Django==1.10 28 | django111: Django >=1.11, <2.0 29 | django20: Django >=2.0, <2.1 30 | django21: Django >=2.1, <2.2 31 | django22: Django >=2.2, <2.3 32 | 33 | psycopg2-binary 34 | 35 | commands = ./run_tests.sh 36 | --------------------------------------------------------------------------------