diff options
Diffstat (limited to 'lib/python2.7/site-packages/south/tests')
66 files changed, 3367 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/south/tests/__init__.py b/lib/python2.7/site-packages/south/tests/__init__.py new file mode 100644 index 0000000..26779e3 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/__init__.py @@ -0,0 +1,109 @@ +from __future__ import print_function + +#import unittest +import os +import sys +from functools import wraps +from django.conf import settings +from south.hacks import hacks + +# Make sure skipping tests is available. +try: + # easiest and best is unittest included in Django>=1.3 + from django.utils import unittest +except ImportError: + # earlier django... use unittest from stdlib + import unittest +# however, skipUnless was only added in Python 2.7; +# if not available, we need to do something else +try: + skipUnless = unittest.skipUnless #@UnusedVariable +except AttributeError: + def skipUnless(condition, message): + def decorator(testfunc): + @wraps(testfunc) + def wrapper(self): + if condition: + # Apply method + testfunc(self) + else: + # The skip exceptions are not available either... + print("Skipping", testfunc.__name__,"--", message) + return wrapper + return decorator + +# ditto for skipIf +try: + skipIf = unittest.skipIf #@UnusedVariable +except AttributeError: + def skipIf(condition, message): + def decorator(testfunc): + @wraps(testfunc) + def wrapper(self): + if condition: + print("Skipping", testfunc.__name__,"--", message) + else: + # Apply method + testfunc(self) + return wrapper + return decorator + +# Add the tests directory so fakeapp is on sys.path +test_root = os.path.dirname(__file__) +sys.path.append(test_root) + +# Note: the individual test files are imported below this. + +class Monkeypatcher(unittest.TestCase): + + """ + Base test class for tests that play with the INSTALLED_APPS setting at runtime. + """ + + def create_fake_app(self, name): + + class Fake: + pass + + fake = Fake() + fake.__name__ = name + try: + fake.migrations = __import__(name + ".migrations", {}, {}, ['migrations']) + except ImportError: + pass + return fake + + def setUp(self): + """ + Changes the Django environment so we can run tests against our test apps. + """ + if hasattr(self, 'installed_apps'): + hacks.store_app_cache_state() + hacks.set_installed_apps(self.installed_apps) + # Make sure dependencies are calculated for new apps + Migrations._dependencies_done = False + + def tearDown(self): + """ + Undoes what setUp did. + """ + if hasattr(self, 'installed_apps'): + hacks.reset_installed_apps() + hacks.restore_app_cache_state() + + +# Try importing all tests if asked for (then we can run 'em) +try: + skiptest = settings.SKIP_SOUTH_TESTS +except: + skiptest = True + +if not skiptest: + from south.tests.db import * + from south.tests.db_mysql import * + from south.tests.db_firebird import * + from south.tests.logic import * + from south.tests.autodetection import * + from south.tests.logger import * + from south.tests.inspector import * + from south.tests.freezer import * diff --git a/lib/python2.7/site-packages/south/tests/autodetection.py b/lib/python2.7/site-packages/south/tests/autodetection.py new file mode 100644 index 0000000..c320d3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/autodetection.py @@ -0,0 +1,360 @@ +from south.tests import unittest + +from south.creator.changes import AutoChanges, InitialChanges +from south.migration.base import Migrations +from south.tests import Monkeypatcher +from south.creator import freezer +from south.orm import FakeORM +from south.v2 import SchemaMigration + +try: + from django.utils.six.moves import reload_module +except ImportError: + # Older django, no python3 support + reload_module = reload + +class TestComparison(unittest.TestCase): + + """ + Tests the comparison methods of startmigration. + """ + + def test_no_change(self): + "Test with a completely unchanged definition." + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['southdemo.Lizard']"}), + ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['southdemo.Lizard']"}), + ), + False, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.related.ForeignKey', ['ohhai', 'there'], {'to': "somewhere", "from": "there"}), + ('django.db.models.fields.related.ForeignKey', ['ohhai', 'there'], {"from": "there", 'to': "somewhere"}), + ), + False, + ) + + + def test_pos_change(self): + "Test with a changed positional argument." + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['hi'], {'to': "foo"}), + ('django.db.models.fields.CharField', [], {'to': "foo"}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', [], {'to': "foo"}), + ('django.db.models.fields.CharField', ['bye'], {'to': "foo"}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['pi'], {'to': "foo"}), + ('django.db.models.fields.CharField', ['pi'], {'to': "foo"}), + ), + False, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['pisdadad'], {'to': "foo"}), + ('django.db.models.fields.CharField', ['pi'], {'to': "foo"}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['hi'], {}), + ('django.db.models.fields.CharField', [], {}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', [], {}), + ('django.db.models.fields.CharField', ['bye'], {}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['pi'], {}), + ('django.db.models.fields.CharField', ['pi'], {}), + ), + False, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['pi'], {}), + ('django.db.models.fields.CharField', ['45fdfdf'], {}), + ), + True, + ) + + + def test_kwd_change(self): + "Test a changed keyword argument" + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['pi'], {'to': "foo"}), + ('django.db.models.fields.CharField', ['pi'], {'to': "blue"}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', [], {'to': "foo"}), + ('django.db.models.fields.CharField', [], {'to': "blue"}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['b'], {'to': "foo"}), + ('django.db.models.fields.CharField', ['b'], {'to': "blue"}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', [], {'to': "foo"}), + ('django.db.models.fields.CharField', [], {}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['a'], {'to': "foo"}), + ('django.db.models.fields.CharField', ['a'], {}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', [], {}), + ('django.db.models.fields.CharField', [], {'to': "foo"}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('django.db.models.fields.CharField', ['a'], {}), + ('django.db.models.fields.CharField', ['a'], {'to': "foo"}), + ), + True, + ) + + + + def test_backcompat_nochange(self): + "Test that the backwards-compatable comparison is working" + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', [], {}), + ('django.db.models.fields.CharField', [], {}), + ), + False, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', ['ack'], {}), + ('django.db.models.fields.CharField', ['ack'], {}), + ), + False, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', [], {'to':'b'}), + ('django.db.models.fields.CharField', [], {'to':'b'}), + ), + False, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', ['hah'], {'to':'you'}), + ('django.db.models.fields.CharField', ['hah'], {'to':'you'}), + ), + False, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', ['hah'], {'to':'you'}), + ('django.db.models.fields.CharField', ['hah'], {'to':'heh'}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', ['hah'], {}), + ('django.db.models.fields.CharField', [], {'to':"orm['appname.hah']"}), + ), + False, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', ['hah'], {}), + ('django.db.models.fields.CharField', [], {'to':'hah'}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', ['hah'], {}), + ('django.db.models.fields.CharField', [], {'to':'rrr'}), + ), + True, + ) + + self.assertEqual( + AutoChanges.different_attributes( + ('models.CharField', ['hah'], {}), + ('django.db.models.fields.IntField', [], {'to':'hah'}), + ), + True, + ) + +class TestNonManagedIgnored(Monkeypatcher): + + installed_apps = ["non_managed"] + + full_defs = { + 'non_managed.legacy': { + 'Meta': {'object_name': 'Legacy', 'db_table': "'legacy_table'", 'managed': 'False'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {}) + } + } + + def test_not_added_init(self): + + migrations = Migrations("non_managed") + changes = InitialChanges(migrations) + change_list = changes.get_changes() + if list(change_list): + self.fail("Initial migration creates table for non-managed model") + + def test_not_added_auto(self): + + empty_defs = { } + class EmptyMigration(SchemaMigration): + "Serves as fake previous migration" + + def forwards(self, orm): + pass + + def backwards(self, orm): + pass + + models = empty_defs + + complete_apps = ['non_managed'] + + migrations = Migrations("non_managed") + empty_orm = FakeORM(EmptyMigration, "non_managed") + changes = AutoChanges( + migrations = migrations, + old_defs = empty_defs, + old_orm = empty_orm, + new_defs = self.full_defs, + ) + change_list = changes.get_changes() + if list(change_list): + self.fail("Auto migration creates table for non-managed model") + + def test_not_deleted_auto(self): + + empty_defs = { } + old_defs = freezer.freeze_apps(["non_managed"]) + class InitialMigration(SchemaMigration): + "Serves as fake previous migration" + + def forwards(self, orm): + pass + + def backwards(self, orm): + pass + + models = self.full_defs + + complete_apps = ['non_managed'] + + migrations = Migrations("non_managed") + initial_orm = FakeORM(InitialMigration, "non_managed") + changes = AutoChanges( + migrations = migrations, + old_defs = self.full_defs, + old_orm = initial_orm, + new_defs = empty_defs, + ) + change_list = changes.get_changes() + if list(change_list): + self.fail("Auto migration deletes table for non-managed model") + + def test_not_modified_auto(self): + + fake_defs = { + 'non_managed.legacy': { + 'Meta': {'object_name': 'Legacy', 'db_table': "'legacy_table'", 'managed': 'False'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), + #'size': ('django.db.models.fields.IntegerField', [], {}) # The "change" is the addition of this field + } + } + class InitialMigration(SchemaMigration): + "Serves as fake previous migration" + + def forwards(self, orm): + pass + + def backwards(self, orm): + pass + + models = fake_defs + + complete_apps = ['non_managed'] + + from non_managed import models as dummy_import_to_force_loading_models # TODO: Does needing this indicate a bug in MokeyPatcher? + reload_module(dummy_import_to_force_loading_models) # really force... + + migrations = Migrations("non_managed") + initial_orm = FakeORM(InitialMigration, "non_managed") + changes = AutoChanges( + migrations = migrations, + old_defs = fake_defs, + old_orm = initial_orm, + new_defs = self.full_defs + ) + change_list = changes.get_changes() + if list(change_list): + self.fail("Auto migration changes table for non-managed model") diff --git a/lib/python2.7/site-packages/south/tests/brokenapp/__init__.py b/lib/python2.7/site-packages/south/tests/brokenapp/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/brokenapp/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0001_depends_on_unmigrated.py b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0001_depends_on_unmigrated.py new file mode 100644 index 0000000..d53f836 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0001_depends_on_unmigrated.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('unknown', '0001_initial')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0002_depends_on_unknown.py b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0002_depends_on_unknown.py new file mode 100644 index 0000000..389af80 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0002_depends_on_unknown.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('fakeapp', '9999_unknown')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0003_depends_on_higher.py b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0003_depends_on_higher.py new file mode 100644 index 0000000..319069b --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0003_depends_on_higher.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('brokenapp', '0004_higher')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0004_higher.py b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0004_higher.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/0004_higher.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/brokenapp/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/brokenapp/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/brokenapp/models.py b/lib/python2.7/site-packages/south/tests/brokenapp/models.py new file mode 100644 index 0000000..a7d84dc --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/brokenapp/models.py @@ -0,0 +1,55 @@ +# -*- coding: UTF-8 -*- + +from django.db import models +from django.contrib.auth.models import User as UserAlias + +def default_func(): + return "yays" + +# An empty case. +class Other1(models.Model): pass + +# Nastiness. +class HorribleModel(models.Model): + "A model to test the edge cases of model parsing" + + ZERO, ONE = range(2) + + # First, some nice fields + name = models.CharField(max_length=255) + short_name = models.CharField(max_length=50) + slug = models.SlugField(unique=True) + + # A ForeignKey, to a model above, and then below + o1 = models.ForeignKey(Other1) + o2 = models.ForeignKey('Other2') + + # Now to something outside + user = models.ForeignKey(UserAlias, related_name="horribles") + + # Unicode! + code = models.CharField(max_length=25, default="↑↑↓↓←→←→BA") + + # Odd defaults! + class_attr = models.IntegerField(default=ZERO) + func = models.CharField(max_length=25, default=default_func) + + # Time to get nasty. Define a non-field choices, and use it + choices = [('hello', '1'), ('world', '2')] + choiced = models.CharField(max_length=20, choices=choices) + + class Meta: + db_table = "my_fave" + verbose_name = "Dr. Strangelove," + \ + """or how I learned to stop worrying +and love the bomb""" + + # Now spread over multiple lines + multiline = \ + models.TextField( + ) + +# Special case. +class Other2(models.Model): + # Try loading a field without a newline after it (inspect hates this) + close_but_no_cigar = models.PositiveIntegerField(primary_key=True)
\ No newline at end of file diff --git a/lib/python2.7/site-packages/south/tests/circular_a/__init__.py b/lib/python2.7/site-packages/south/tests/circular_a/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/circular_a/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/circular_a/migrations/0001_first.py b/lib/python2.7/site-packages/south/tests/circular_a/migrations/0001_first.py new file mode 100644 index 0000000..b0d90eb --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/circular_a/migrations/0001_first.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('circular_b', '0001_first')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/circular_a/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/circular_a/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/circular_a/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/circular_a/models.py b/lib/python2.7/site-packages/south/tests/circular_a/models.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/circular_a/models.py diff --git a/lib/python2.7/site-packages/south/tests/circular_b/__init__.py b/lib/python2.7/site-packages/south/tests/circular_b/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/circular_b/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/circular_b/migrations/0001_first.py b/lib/python2.7/site-packages/south/tests/circular_b/migrations/0001_first.py new file mode 100644 index 0000000..b11b120 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/circular_b/migrations/0001_first.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('circular_a', '0001_first')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/circular_b/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/circular_b/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/circular_b/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/circular_b/models.py b/lib/python2.7/site-packages/south/tests/circular_b/models.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/circular_b/models.py diff --git a/lib/python2.7/site-packages/south/tests/db.py b/lib/python2.7/site-packages/south/tests/db.py new file mode 100644 index 0000000..e63c563 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/db.py @@ -0,0 +1,1060 @@ +import datetime +from warnings import filterwarnings + +from south.db import db, generic +from django.db import connection, models, IntegrityError as DjangoIntegrityError + +from south.tests import unittest, skipIf, skipUnless +from south.utils.py3 import text_type, with_metaclass + +# Create a list of error classes from the various database libraries +errors = [] +try: + from psycopg2 import ProgrammingError + errors.append(ProgrammingError) +except ImportError: + pass +errors = tuple(errors) + +# On SQL Server, the backend's IntegrityError is not (a subclass of) Django's. +try: + from sql_server.pyodbc.base import IntegrityError as SQLServerIntegrityError + IntegrityError = (DjangoIntegrityError, SQLServerIntegrityError) +except ImportError: + IntegrityError = DjangoIntegrityError + +try: + from south.db import mysql +except ImportError: + mysql = None + + +class TestOperations(unittest.TestCase): + + """ + Tests if the various DB abstraction calls work. + Can only test a limited amount due to DB differences. + """ + + def setUp(self): + db.debug = False + try: + import MySQLdb + except ImportError: + pass + else: + filterwarnings('ignore', category=MySQLdb.Warning) + db.clear_deferred_sql() + db.start_transaction() + + def tearDown(self): + db.rollback_transaction() + + def test_create(self): + """ + Test creation of tables. + """ + cursor = connection.cursor() + # It needs to take at least 2 args + self.assertRaises(TypeError, db.create_table) + self.assertRaises(TypeError, db.create_table, "test1") + # Empty tables (i.e. no columns) are not fine, so make at least 1 + db.create_table("test1", [('email_confirmed', models.BooleanField(default=False))]) + # And should exist + cursor.execute("SELECT * FROM test1") + # Make sure we can't do the same query on an empty table + try: + cursor.execute("SELECT * FROM nottheretest1") + except: + pass + else: + self.fail("Non-existent table could be selected!") + + @skipUnless(db.raises_default_errors, 'This database does not raise errors on missing defaults.') + def test_create_default(self): + """ + Test creation of tables, make sure defaults are not left in the database + """ + db.create_table("test_create_default", [('a', models.IntegerField()), + ('b', models.IntegerField(default=17))]) + cursor = connection.cursor() + self.assertRaises(IntegrityError, cursor.execute, "INSERT INTO test_create_default(a) VALUES (17)") + + def test_delete(self): + """ + Test deletion of tables. + """ + cursor = connection.cursor() + db.create_table("test_deltable", [('email_confirmed', models.BooleanField(default=False))]) + db.delete_table("test_deltable") + # Make sure it went + try: + cursor.execute("SELECT * FROM test_deltable") + except: + pass + else: + self.fail("Just-deleted table could be selected!") + + def test_nonexistent_delete(self): + """ + Test deletion of nonexistent tables. + """ + try: + db.delete_table("test_nonexistdeltable") + except: + pass + else: + self.fail("Non-existent table could be deleted!") + + def test_foreign_keys(self): + """ + Tests foreign key creation, especially uppercase (see #61) + """ + Test = db.mock_model(model_name='Test', db_table='test5a', + db_tablespace='', pk_field_name='ID', + pk_field_type=models.AutoField, pk_field_args=[]) + db.create_table("test5a", [('ID', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True))]) + db.create_table("test5b", [ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('UNIQUE', models.ForeignKey(Test)), + ]) + db.execute_deferred_sql() + + @skipUnless(db.supports_foreign_keys, 'Foreign keys can only be deleted on ' + 'engines that support them.') + def test_recursive_foreign_key_delete(self): + """ + Test that recursive foreign keys are deleted correctly (see #1065) + """ + Test = db.mock_model(model_name='Test', db_table='test_rec_fk_del', + db_tablespace='', pk_field_name='id', + pk_field_type=models.AutoField, pk_field_args=[]) + db.create_table('test_rec_fk_del', [ + ('id', models.AutoField(primary_key=True, auto_created=True)), + ('fk', models.ForeignKey(Test)), + ]) + db.execute_deferred_sql() + db.delete_foreign_key('test_rec_fk_del', 'fk_id') + + def test_rename(self): + """ + Test column renaming + """ + cursor = connection.cursor() + db.create_table("test_rn", [('spam', models.BooleanField(default=False))]) + # Make sure we can select the column + cursor.execute("SELECT spam FROM test_rn") + # Rename it + db.rename_column("test_rn", "spam", "eggs") + cursor.execute("SELECT eggs FROM test_rn") + db.commit_transaction() + db.start_transaction() + try: + cursor.execute("SELECT spam FROM test_rn") + except: + pass + else: + self.fail("Just-renamed column could be selected!") + db.rollback_transaction() + db.delete_table("test_rn") + db.start_transaction() + + def test_dry_rename(self): + """ + Test column renaming while --dry-run is turned on (should do nothing) + See ticket #65 + """ + cursor = connection.cursor() + db.create_table("test_drn", [('spam', models.BooleanField(default=False))]) + # Make sure we can select the column + cursor.execute("SELECT spam FROM test_drn") + # Rename it + db.dry_run = True + db.rename_column("test_drn", "spam", "eggs") + db.dry_run = False + cursor.execute("SELECT spam FROM test_drn") + db.commit_transaction() + db.start_transaction() + try: + cursor.execute("SELECT eggs FROM test_drn") + except: + pass + else: + self.fail("Dry-renamed new column could be selected!") + db.rollback_transaction() + db.delete_table("test_drn") + db.start_transaction() + + def test_table_rename(self): + """ + Test column renaming + """ + cursor = connection.cursor() + db.create_table("testtr", [('spam', models.BooleanField(default=False))]) + # Make sure we can select the column + cursor.execute("SELECT spam FROM testtr") + # Rename it + db.rename_table("testtr", "testtr2") + cursor.execute("SELECT spam FROM testtr2") + db.commit_transaction() + db.start_transaction() + try: + cursor.execute("SELECT spam FROM testtr") + except: + pass + else: + self.fail("Just-renamed column could be selected!") + db.rollback_transaction() + db.delete_table("testtr2") + db.start_transaction() + + def test_percents_in_defaults(self): + """ + Test that % in a default gets escaped to %%. + """ + try: + db.create_table("testpind", [('cf', models.CharField(max_length=255, default="It should be 2%!"))]) + except IndexError: + self.fail("% was not properly escaped in column SQL.") + db.delete_table("testpind") + + def test_index(self): + """ + Test the index operations + """ + db.create_table("test3", [ + ('SELECT', models.BooleanField(default=False)), + ('eggs', models.IntegerField(unique=True)), + ]) + db.execute_deferred_sql() + # Add an index on that column + db.create_index("test3", ["SELECT"]) + # Add another index on two columns + db.create_index("test3", ["SELECT", "eggs"]) + # Delete them both + db.delete_index("test3", ["SELECT"]) + db.delete_index("test3", ["SELECT", "eggs"]) + # Delete the unique index/constraint + if db.backend_name != "sqlite3": + db.delete_unique("test3", ["eggs"]) + db.delete_table("test3") + + def test_primary_key(self): + """ + Test the primary key operations + """ + + db.create_table("test_pk", [ + ('id', models.IntegerField(primary_key=True)), + ('new_pkey', models.IntegerField()), + ('eggs', models.IntegerField(unique=True)), + ]) + db.execute_deferred_sql() + # Remove the default primary key, and make eggs it + db.delete_primary_key("test_pk") + db.create_primary_key("test_pk", "new_pkey") + # Try inserting a now-valid row pair + db.execute("INSERT INTO test_pk (id, new_pkey, eggs) VALUES (1, 2, 3)") + db.execute("INSERT INTO test_pk (id, new_pkey, eggs) VALUES (1, 3, 4)") + db.delete_table("test_pk") + + def test_primary_key_implicit(self): + """ + Tests that changing primary key implicitly fails. + """ + db.create_table("test_pki", [ + ('id', models.IntegerField(primary_key=True)), + ('new_pkey', models.IntegerField()), + ('eggs', models.IntegerField(unique=True)), + ]) + db.execute_deferred_sql() + # Fiddle with alter_column to attempt to make it remove the primary key + db.alter_column("test_pki", "id", models.IntegerField()) + db.alter_column("test_pki", "new_pkey", models.IntegerField(primary_key=True)) + # Try inserting a should-be-valid row pair + db.execute("INSERT INTO test_pki (id, new_pkey, eggs) VALUES (1, 2, 3)") + db.execute("INSERT INTO test_pki (id, new_pkey, eggs) VALUES (2, 2, 4)") + db.delete_table("test_pki") + + def test_add_columns(self): + """ + Test adding columns + """ + db.create_table("test_addc", [ + ('spam', models.BooleanField(default=False)), + ('eggs', models.IntegerField()), + ]) + # Add a column + db.add_column("test_addc", "add1", models.IntegerField(default=3)) + User = db.mock_model(model_name='User', db_table='auth_user', db_tablespace='', pk_field_name='id', pk_field_type=models.AutoField, pk_field_args=[], pk_field_kwargs={}) + # insert some data so we can test the default value of the added fkey + db.execute("INSERT INTO test_addc (spam, eggs, add1) VALUES (%s, 1, 2)", [False]) + db.add_column("test_addc", "user", models.ForeignKey(User, null=True)) + db.execute_deferred_sql() + # try selecting from the user_id column to make sure it was actually created + val = db.execute("SELECT user_id FROM test_addc")[0][0] + self.assertEquals(val, None) + db.delete_column("test_addc", "add1") + # make sure adding an indexed field works + db.add_column("test_addc", "add2", models.CharField(max_length=15, db_index=True, default='pi')) + db.execute_deferred_sql() + db.delete_table("test_addc") + + def test_delete_columns(self): + """ + Test deleting columns + """ + db.create_table("test_delc", [ + ('spam', models.BooleanField(default=False)), + ('eggs', models.IntegerField(db_index=True, unique=True)), + ]) + db.delete_column("test_delc", "eggs") + + def test_add_nullbool_column(self): + """ + Test adding NullBoolean columns + """ + db.create_table("test_addnbc", [ + ('spam', models.BooleanField(default=False)), + ('eggs', models.IntegerField()), + ]) + # Add a column + db.add_column("test_addnbc", "add1", models.NullBooleanField()) + # Add a column with a default + db.add_column("test_addnbc", "add2", models.NullBooleanField(default=True)) + # insert some data so we can test the default values of the added column + db.execute("INSERT INTO test_addnbc (spam, eggs) VALUES (%s, 1)", [False]) + # try selecting from the new columns to make sure they were properly created + false, null1, null2 = db.execute("SELECT spam,add1,add2 FROM test_addnbc")[0][0:3] + self.assertIsNone(null1, "Null boolean field with no value inserted returns non-null") + self.assertIsNone(null2, "Null boolean field (added with default) with no value inserted returns non-null") + self.assertEquals(false, False) + db.delete_table("test_addnbc") + + def test_alter_columns(self): + """ + Test altering columns + """ + db.create_table("test_alterc", [ + ('spam', models.BooleanField(default=False)), + ('eggs', models.IntegerField()), + ]) + db.execute_deferred_sql() + # Change eggs to be a FloatField + db.alter_column("test_alterc", "eggs", models.FloatField()) + db.execute_deferred_sql() + db.delete_table("test_alterc") + db.execute_deferred_sql() + + def test_alter_char_default(self): + """ + Test altering column defaults with char fields + """ + db.create_table("test_altercd", [ + ('spam', models.CharField(max_length=30)), + ('eggs', models.IntegerField()), + ]) + # Change spam default + db.alter_column("test_altercd", "spam", models.CharField(max_length=30, default="loof", null=True)) + # Assert the default is not in the database + db.execute("INSERT INTO test_altercd (eggs) values (12)") + null = db.execute("SELECT spam FROM test_altercd")[0][0] + self.assertFalse(null, "Default for char field was installed into database") + + # Change again to a column with default and not null + db.alter_column("test_altercd", "spam", models.CharField(max_length=30, default="loof", null=False)) + # Assert the default is not in the database + if 'oracle' in db.backend_name: + # Oracle special treatment -- nulls are always allowed in char columns, so + # inserting doesn't raise an integrity error; so we check again as above + db.execute("DELETE FROM test_altercd") + db.execute("INSERT INTO test_altercd (eggs) values (12)") + null = db.execute("SELECT spam FROM test_altercd")[0][0] + self.assertFalse(null, "Default for char field was installed into database") + else: + # For other backends, insert should now just fail + self.assertRaises(IntegrityError, + db.execute, "INSERT INTO test_altercd (eggs) values (12)") + + @skipIf('oracle' in db.backend_name, "Oracle does not differentiate empty trings from null") + def test_default_empty_string(self): + """ + Test altering column defaults with char fields + """ + db.create_table("test_cd_empty", [ + ('spam', models.CharField(max_length=30, default='')), + ('eggs', models.CharField(max_length=30)), + ]) + # Create a record + db.execute("INSERT INTO test_cd_empty (spam, eggs) values ('1','2')") + # Add a column + db.add_column("test_cd_empty", "ham", models.CharField(max_length=30, default='')) + + empty = db.execute("SELECT ham FROM test_cd_empty")[0][0] + self.assertEquals(empty, "", "Empty Default for char field isn't empty string") + + @skipUnless('oracle' in db.backend_name, "Oracle does not differentiate empty trings from null") + def test_oracle_strings_null(self): + """ + Test that under Oracle, CherFields are created as null even when specified not-null, + because otherwise they would not be able to hold empty strings (which Oracle equates + with nulls). + Verify fix of #1269. + """ + db.create_table("test_ora_char_nulls", [ + ('spam', models.CharField(max_length=30, null=True)), + ('eggs', models.CharField(max_length=30)), + ]) + db.add_column("test_ora_char_nulls", "ham", models.CharField(max_length=30)) + db.alter_column("test_ora_char_nulls", "spam", models.CharField(max_length=30, null=False)) + # So, by the look of it, we should now have three not-null columns + db.execute("INSERT INTO test_ora_char_nulls VALUES (NULL, NULL, NULL)") + + + def test_mysql_defaults(self): + """ + Test MySQL default handling for BLOB and TEXT. + """ + db.create_table("test_altermyd", [ + ('spam', models.BooleanField(default=False)), + ('eggs', models.TextField()), + ]) + # Change eggs to be a FloatField + db.alter_column("test_altermyd", "eggs", models.TextField(null=True)) + db.delete_table("test_altermyd") + + def test_alter_column_postgres_multiword(self): + """ + Tests altering columns with multiple words in Postgres types (issue #125) + e.g. 'datetime with time zone', look at django/db/backends/postgresql/creation.py + """ + db.create_table("test_multiword", [ + ('col_datetime', models.DateTimeField(null=True)), + ('col_integer', models.PositiveIntegerField(null=True)), + ('col_smallint', models.PositiveSmallIntegerField(null=True)), + ('col_float', models.FloatField(null=True)), + ]) + + # test if 'double precision' is preserved + db.alter_column('test_multiword', 'col_float', models.FloatField('float', null=True)) + + # test if 'CHECK ("%(column)s" >= 0)' is stripped + db.alter_column('test_multiword', 'col_integer', models.PositiveIntegerField(null=True)) + db.alter_column('test_multiword', 'col_smallint', models.PositiveSmallIntegerField(null=True)) + + # test if 'with timezone' is preserved + if db.backend_name == "postgres": + db.execute("INSERT INTO test_multiword (col_datetime) VALUES ('2009-04-24 14:20:55+02')") + db.alter_column('test_multiword', 'col_datetime', models.DateTimeField(auto_now=True)) + assert db.execute("SELECT col_datetime = '2009-04-24 14:20:55+02' FROM test_multiword")[0][0] + + db.delete_table("test_multiword") + + @skipUnless(db.has_check_constraints, 'Only applies to databases that ' + 'support CHECK constraints.') + def test_alter_constraints(self): + """ + Tests that going from a PostiveIntegerField to an IntegerField drops + the constraint on the database. + """ + # Make the test table + db.create_table("test_alterc", [ + ('num', models.PositiveIntegerField()), + ]) + db.execute_deferred_sql() + # Add in some test values + db.execute("INSERT INTO test_alterc (num) VALUES (1)") + db.execute("INSERT INTO test_alterc (num) VALUES (2)") + # Ensure that adding a negative number is bad + db.commit_transaction() + db.start_transaction() + try: + db.execute("INSERT INTO test_alterc (num) VALUES (-3)") + except: + db.rollback_transaction() + else: + self.fail("Could insert a negative integer into a PositiveIntegerField.") + # Alter it to a normal IntegerField + db.alter_column("test_alterc", "num", models.IntegerField()) + db.execute_deferred_sql() + # It should now work + db.execute("INSERT INTO test_alterc (num) VALUES (-3)") + db.delete_table("test_alterc") + # We need to match up for tearDown + db.start_transaction() + + @skipIf(db.backend_name == "sqlite3", "SQLite backend doesn't support this " + "yet.") + def test_unique(self): + """ + Tests creating/deleting unique constraints. + """ + db.create_table("test_unique2", [ + ('id', models.AutoField(primary_key=True)), + ]) + db.create_table("test_unique", [ + ('spam', models.BooleanField(default=False)), + ('eggs', models.IntegerField()), + ('ham', models.ForeignKey(db.mock_model('Unique2', 'test_unique2'))), + ]) + db.execute_deferred_sql() + # Add a constraint + db.create_unique("test_unique", ["spam"]) + db.execute_deferred_sql() + # Shouldn't do anything during dry-run + db.dry_run = True + db.delete_unique("test_unique", ["spam"]) + db.dry_run = False + db.delete_unique("test_unique", ["spam"]) + db.create_unique("test_unique", ["spam"]) + # Special preparations for Sql Server + if db.backend_name == "pyodbc": + db.execute("SET IDENTITY_INSERT test_unique2 ON;") + db.execute("INSERT INTO test_unique2 (id) VALUES (1)") + db.execute("INSERT INTO test_unique2 (id) VALUES (2)") + db.commit_transaction() + db.start_transaction() + + + # Test it works + TRUE = (True,) + FALSE = (False,) + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 0, 1)", TRUE) + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 1, 2)", FALSE) + try: + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 2, 1)", FALSE) + except: + db.rollback_transaction() + else: + self.fail("Could insert non-unique item.") + + # Drop that, add one only on eggs + db.delete_unique("test_unique", ["spam"]) + db.execute("DELETE FROM test_unique") + db.create_unique("test_unique", ["eggs"]) + db.start_transaction() + + # Test similarly + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 0, 1)", TRUE) + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 1, 2)", FALSE) + try: + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 1, 1)", TRUE) + except: + db.rollback_transaction() + else: + self.fail("Could insert non-unique item.") + + # Drop those, test combined constraints + db.delete_unique("test_unique", ["eggs"]) + db.execute("DELETE FROM test_unique") + db.create_unique("test_unique", ["spam", "eggs", "ham_id"]) + db.start_transaction() + # Test similarly + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 0, 1)", TRUE) + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 1, 1)", FALSE) + try: + db.execute("INSERT INTO test_unique (spam, eggs, ham_id) VALUES (%s, 0, 1)", TRUE) + except: + db.rollback_transaction() + else: + self.fail("Could insert non-unique pair.") + db.delete_unique("test_unique", ["spam", "eggs", "ham_id"]) + db.start_transaction() + + def test_alter_unique(self): + """ + Tests that unique constraints are not affected when + altering columns (that's handled by create_/delete_unique) + """ + db.create_table("test_alter_unique", [ + ('spam', models.IntegerField()), + ('eggs', models.IntegerField(unique=True)), + ]) + db.execute_deferred_sql() + + # Make sure the unique constraint is created + db.execute('INSERT INTO test_alter_unique (spam, eggs) VALUES (0, 42)') + db.commit_transaction() + db.start_transaction() + try: + db.execute("INSERT INTO test_alter_unique (spam, eggs) VALUES (1, 42)") + except: + pass + else: + self.fail("Could insert the same integer twice into a unique field.") + db.rollback_transaction() + + # Alter without unique=True (should not affect anything) + db.alter_column("test_alter_unique", "eggs", models.IntegerField()) + + # Insertion should still fail + db.start_transaction() + try: + db.execute("INSERT INTO test_alter_unique (spam, eggs) VALUES (1, 42)") + except: + pass + else: + self.fail("Could insert the same integer twice into a unique field after alter_column with unique=False.") + db.rollback_transaction() + + # Delete the unique index/constraint + if db.backend_name != "sqlite3": + db.delete_unique("test_alter_unique", ["eggs"]) + db.delete_table("test_alter_unique") + db.start_transaction() + + # Test multi-field constraint + db.create_table("test_alter_unique2", [ + ('spam', models.IntegerField()), + ('eggs', models.IntegerField()), + ]) + db.create_unique('test_alter_unique2', ('spam', 'eggs')) + db.execute_deferred_sql() + db.execute('INSERT INTO test_alter_unique2 (spam, eggs) VALUES (0, 42)') + db.commit_transaction() + # Verify that constraint works + db.start_transaction() + try: + db.execute("INSERT INTO test_alter_unique2 (spam, eggs) VALUES (1, 42)") + except: + self.fail("Looks like multi-field unique constraint applied to only one field.") + db.rollback_transaction() + db.start_transaction() + try: + db.execute("INSERT INTO test_alter_unique2 (spam, eggs) VALUES (0, 43)") + except: + self.fail("Looks like multi-field unique constraint applied to only one field.") + db.rollback_transaction() + db.start_transaction() + try: + db.execute("INSERT INTO test_alter_unique2 (spam, eggs) VALUES (0, 42)") + except: + pass + else: + self.fail("Could insert the same pair twice into unique-together fields.") + db.rollback_transaction() + # Altering one column should not drop or modify multi-column constraint + db.alter_column("test_alter_unique2", "eggs", models.PositiveIntegerField()) + db.start_transaction() + try: + db.execute("INSERT INTO test_alter_unique2 (spam, eggs) VALUES (1, 42)") + except: + self.fail("Altering one column broken multi-column unique constraint.") + db.rollback_transaction() + db.start_transaction() + try: + db.execute("INSERT INTO test_alter_unique2 (spam, eggs) VALUES (0, 43)") + except: + self.fail("Altering one column broken multi-column unique constraint.") + db.rollback_transaction() + db.start_transaction() + try: + db.execute("INSERT INTO test_alter_unique2 (spam, eggs) VALUES (0, 42)") + except: + pass + else: + self.fail("Could insert the same pair twice into unique-together fields after alter_column with unique=False.") + db.rollback_transaction() + db.delete_table("test_alter_unique2") + db.start_transaction() + + def test_capitalised_constraints(self): + """ + Under PostgreSQL at least, capitalised constraints must be quoted. + """ + db.create_table("test_capconst", [ + ('SOMECOL', models.PositiveIntegerField(primary_key=True)), + ]) + # Alter it so it's not got the check constraint + db.alter_column("test_capconst", "SOMECOL", models.IntegerField()) + + def test_text_default(self): + """ + MySQL cannot have blank defaults on TEXT columns. + """ + db.create_table("test_textdef", [ + ('textcol', models.TextField(blank=True)), + ]) + + def test_text_to_char(self): + """ + On Oracle, you can't simply ALTER TABLE MODIFY a textfield to a charfield + """ + value = "kawabanga" + db.create_table("test_text_to_char", [ + ('textcol', models.TextField()), + ]) + db.execute_deferred_sql() + db.execute("INSERT INTO test_text_to_char VALUES (%s)", [value]) + db.alter_column("test_text_to_char", "textcol", models.CharField(max_length=100)) + db.execute_deferred_sql() + after = db.execute("select * from test_text_to_char")[0][0] + self.assertEqual(value, after, "Change from text to char altered value [ %r != %r ]" % (value, after)) + + def test_char_to_text(self): + """ + On Oracle, you can't simply ALTER TABLE MODIFY a charfield to a textfield either + """ + value = "agnabawak" + db.create_table("test_char_to_text", [ + ('textcol', models.CharField(max_length=100)), + ]) + db.execute_deferred_sql() + db.execute("INSERT INTO test_char_to_text VALUES (%s)", [value]) + db.alter_column("test_char_to_text", "textcol", models.TextField()) + db.execute_deferred_sql() + after = db.execute("select * from test_char_to_text")[0][0] + after = text_type(after) # Oracle text fields return a sort of lazy string -- force evaluation + self.assertEqual(value, after, "Change from char to text altered value [ %r != %r ]" % (value, after)) + + @skipUnless(db.raises_default_errors, 'This database does not raise errors on missing defaults.') + def test_datetime_default(self): + """ + Test that defaults are correctly not created for datetime columns + """ + end_of_world = datetime.datetime(2012, 12, 21, 0, 0, 1) + + try: + from django.utils import timezone + except ImportError: + pass + else: + from django.conf import settings + if getattr(settings, 'USE_TZ', False): + end_of_world = end_of_world.replace(tzinfo=timezone.utc) + + db.create_table("test_datetime_def", [ + ('col0', models.IntegerField(null=True)), + ('col1', models.DateTimeField(default=end_of_world)), + ('col2', models.DateTimeField(null=True)), + ]) + db.execute_deferred_sql() + # insert a row + db.execute("INSERT INTO test_datetime_def (col0, col1, col2) values (null,%s,null)", [end_of_world]) + db.alter_column("test_datetime_def", "col2", models.DateTimeField(default=end_of_world)) + db.add_column("test_datetime_def", "col3", models.DateTimeField(default=end_of_world)) + db.execute_deferred_sql() + db.commit_transaction() + # In the single existing row, we now expect col1=col2=col3=end_of_world... + db.start_transaction() + ends = db.execute("select col1,col2,col3 from test_datetime_def")[0] + self.failUnlessEqual(len(ends), 3) + for e in ends: + self.failUnlessEqual(e, end_of_world) + db.commit_transaction() + # ...but there should not be a default in the database for col1 or col3 + for cols in ["col1,col2", "col2,col3"]: + db.start_transaction() + statement = "insert into test_datetime_def (col0,%s) values (null,%%s,%%s)" % cols + self.assertRaises( + IntegrityError, + db.execute, statement, [end_of_world, end_of_world] + ) + db.rollback_transaction() + + db.start_transaction() # To preserve the sanity and semantics of this test class + + def test_add_unique_fk(self): + """ + Test adding a ForeignKey with unique=True or a OneToOneField + """ + db.create_table("test_add_unique_fk", [ + ('spam', models.BooleanField(default=False)) + ]) + + db.add_column("test_add_unique_fk", "mock1", models.ForeignKey(db.mock_model('User', 'auth_user'), null=True, unique=True)) + db.add_column("test_add_unique_fk", "mock2", models.OneToOneField(db.mock_model('User', 'auth_user'), null=True)) + db.execute_deferred_sql() + + db.delete_table("test_add_unique_fk") + + @skipUnless(db.has_check_constraints, 'Only applies to databases that ' + 'support CHECK constraints.') + def test_column_constraint(self): + """ + Tests that the value constraint of PositiveIntegerField is enforced on + the database level. + """ + db.create_table("test_column_constraint", [ + ('spam', models.PositiveIntegerField()), + ]) + db.execute_deferred_sql() + + # Make sure we can't insert negative values + db.commit_transaction() + db.start_transaction() + try: + db.execute("INSERT INTO test_column_constraint VALUES (-42)") + except: + pass + else: + self.fail("Could insert a negative value into a PositiveIntegerField.") + db.rollback_transaction() + + # remove constraint + db.alter_column("test_column_constraint", "spam", models.IntegerField()) + db.execute_deferred_sql() + # make sure the insertion works now + db.execute('INSERT INTO test_column_constraint VALUES (-42)') + db.execute('DELETE FROM test_column_constraint') + + # add it back again + db.alter_column("test_column_constraint", "spam", models.PositiveIntegerField()) + db.execute_deferred_sql() + # it should fail again + db.start_transaction() + try: + db.execute("INSERT INTO test_column_constraint VALUES (-42)") + except: + pass + else: + self.fail("Could insert a negative value after changing an IntegerField to a PositiveIntegerField.") + db.rollback_transaction() + + db.delete_table("test_column_constraint") + db.start_transaction() + + def test_sql_defaults(self): + """ + Test that sql default value is correct for non-string field types. + Datetimes are handled in test_datetime_default. + """ + + class CustomField(with_metaclass(models.SubfieldBase, models.CharField)): + description = 'CustomField' + def get_default(self): + if self.has_default(): + if callable(self.default): + return self.default() + return self.default + return super(CustomField, self).get_default() + def get_prep_value(self, value): + if not value: + return value + return ','.join(map(str, value)) + def to_python(self, value): + if not value or isinstance(value, list): + return value + return list(map(int, value.split(','))) + + false_value = db.has_booleans and 'False' or '0' + defaults = ( + (models.CharField(default='sukasuka'), 'DEFAULT \'sukasuka'), + (models.BooleanField(default=False), 'DEFAULT %s' % false_value), + (models.IntegerField(default=42), 'DEFAULT 42'), + (CustomField(default=[2012, 2018, 2021, 2036]), 'DEFAULT \'2012,2018,2021,2036') + ) + for field, sql_test_str in defaults: + sql = db.column_sql('fish', 'YAAAAAAZ', field) + if sql_test_str not in sql: + self.fail("default sql value was not properly generated for field %r.\nSql was %s" % (field, sql)) + + def test_make_added_foreign_key_not_null(self): + # Table for FK to target + User = db.mock_model(model_name='User', db_table='auth_user', db_tablespace='', pk_field_name='id', pk_field_type=models.AutoField, pk_field_args=[], pk_field_kwargs={}) + # Table with no foreign key + db.create_table("test_fk", [ + ('eggs', models.IntegerField()), + ]) + db.execute_deferred_sql() + + # Add foreign key + db.add_column("test_fk", 'foreik', models.ForeignKey(User, null=True)) + db.execute_deferred_sql() + + # Make the FK not null + db.alter_column("test_fk", "foreik_id", models.ForeignKey(User)) + db.execute_deferred_sql() + + def test_make_foreign_key_null(self): + # Table for FK to target + User = db.mock_model(model_name='User', db_table='auth_user', db_tablespace='', pk_field_name='id', pk_field_type=models.AutoField, pk_field_args=[], pk_field_kwargs={}) + # Table with no foreign key + db.create_table("test_make_fk_null", [ + ('eggs', models.IntegerField()), + ('foreik', models.ForeignKey(User)) + ]) + db.execute_deferred_sql() + + # Make the FK null + db.alter_column("test_make_fk_null", "foreik_id", models.ForeignKey(User, null=True)) + db.execute_deferred_sql() + + def test_change_foreign_key_target(self): + # Tables for FK to target + User = db.mock_model(model_name='User', db_table='auth_user', db_tablespace='', pk_field_name='id', pk_field_type=models.AutoField, pk_field_args=[], pk_field_kwargs={}) + db.create_table("test_fk_changed_target", [ + ('eggs', models.IntegerField(primary_key=True)), + ]) + Egg = db.mock_model(model_name='Egg', db_table='test_fk_changed_target', db_tablespace='', pk_field_name='eggs', pk_field_type=models.AutoField, pk_field_args=[], pk_field_kwargs={}) + # Table with a foreign key to the wrong table + db.create_table("test_fk_changing", [ + ('egg', models.ForeignKey(User, null=True)), + ]) + db.execute_deferred_sql() + + # Change foreign key pointing + db.alter_column("test_fk_changing", "egg_id", models.ForeignKey(Egg, null=True)) + db.execute_deferred_sql() + + # Test that it is pointing at the right table now + try: + non_user_id = db.execute("SELECT MAX(id) FROM auth_user")[0][0] + 1 + except (TypeError, IndexError): + # Got a "None" or no records, treat as 0 + non_user_id = 17 + db.execute("INSERT INTO test_fk_changed_target (eggs) VALUES (%s)", [non_user_id]) + db.execute("INSERT INTO test_fk_changing (egg_id) VALUES (%s)", [non_user_id]) + db.commit_transaction() + db.start_transaction() # The test framework expects tests to end in transaction + + def test_alter_double_indexed_column(self): + # Table for FK to target + User = db.mock_model(model_name='User', db_table='auth_user', db_tablespace='', pk_field_name='id', pk_field_type=models.AutoField, pk_field_args=[], pk_field_kwargs={}) + # Table with no foreign key + db.create_table("test_2indexed", [ + ('eggs', models.IntegerField()), + ('foreik', models.ForeignKey(User)) + ]) + db.create_unique("test_2indexed", ["eggs", "foreik_id"]) + db.execute_deferred_sql() + + # Make the FK null + db.alter_column("test_2indexed", "foreik_id", models.ForeignKey(User, null=True)) + db.execute_deferred_sql() + +class TestCacheGeneric(unittest.TestCase): + base_ops_cls = generic.DatabaseOperations + def setUp(self): + class CacheOps(self.base_ops_cls): + def __init__(self): + self._constraint_cache = {} + self.cache_filled = 0 + self.settings = {'NAME': 'db'} + + def _fill_constraint_cache(self, db, table): + self.cache_filled += 1 + self._constraint_cache.setdefault(db, {}) + self._constraint_cache[db].setdefault(table, {}) + + @generic.invalidate_table_constraints + def clear_con(self, table): + pass + + @generic.copy_column_constraints + def cp_column(self, table, column_old, column_new): + pass + + @generic.delete_column_constraints + def rm_column(self, table, column): + pass + + @generic.copy_column_constraints + @generic.delete_column_constraints + def mv_column(self, table, column_old, column_new): + pass + + def _get_setting(self, attr): + return self.settings[attr] + self.CacheOps = CacheOps + + def test_cache(self): + ops = self.CacheOps() + self.assertEqual(0, ops.cache_filled) + self.assertFalse(ops.lookup_constraint('db', 'table')) + self.assertEqual(1, ops.cache_filled) + self.assertFalse(ops.lookup_constraint('db', 'table')) + self.assertEqual(1, ops.cache_filled) + ops.clear_con('table') + self.assertEqual(1, ops.cache_filled) + self.assertFalse(ops.lookup_constraint('db', 'table')) + self.assertEqual(2, ops.cache_filled) + self.assertFalse(ops.lookup_constraint('db', 'table', 'column')) + self.assertEqual(2, ops.cache_filled) + + cache = ops._constraint_cache + cache['db']['table']['column'] = 'constraint' + self.assertEqual('constraint', ops.lookup_constraint('db', 'table', 'column')) + self.assertEqual([('column', 'constraint')], ops.lookup_constraint('db', 'table')) + self.assertEqual(2, ops.cache_filled) + + # invalidate_table_constraints + ops.clear_con('new_table') + self.assertEqual('constraint', ops.lookup_constraint('db', 'table', 'column')) + self.assertEqual(2, ops.cache_filled) + + self.assertFalse(ops.lookup_constraint('db', 'new_table')) + self.assertEqual(3, ops.cache_filled) + + # delete_column_constraints + cache['db']['table']['column'] = 'constraint' + self.assertEqual('constraint', ops.lookup_constraint('db', 'table', 'column')) + ops.rm_column('table', 'column') + self.assertEqual([], ops.lookup_constraint('db', 'table', 'column')) + self.assertEqual([], ops.lookup_constraint('db', 'table', 'noexist_column')) + + # copy_column_constraints + cache['db']['table']['column'] = 'constraint' + self.assertEqual('constraint', ops.lookup_constraint('db', 'table', 'column')) + ops.cp_column('table', 'column', 'column_new') + self.assertEqual('constraint', ops.lookup_constraint('db', 'table', 'column_new')) + self.assertEqual('constraint', ops.lookup_constraint('db', 'table', 'column')) + + # copy + delete + cache['db']['table']['column'] = 'constraint' + self.assertEqual('constraint', ops.lookup_constraint('db', 'table', 'column')) + ops.mv_column('table', 'column', 'column_new') + self.assertEqual('constraint', ops.lookup_constraint('db', 'table', 'column_new')) + self.assertEqual([], ops.lookup_constraint('db', 'table', 'column')) + + def test_valid(self): + ops = self.CacheOps() + # none of these should vivify a table into a valid state + self.assertFalse(ops._is_valid_cache('db', 'table')) + self.assertFalse(ops._is_valid_cache('db', 'table')) + ops.clear_con('table') + self.assertFalse(ops._is_valid_cache('db', 'table')) + ops.rm_column('table', 'column') + self.assertFalse(ops._is_valid_cache('db', 'table')) + + # these should change the cache state + ops.lookup_constraint('db', 'table') + self.assertTrue(ops._is_valid_cache('db', 'table')) + ops.lookup_constraint('db', 'table', 'column') + self.assertTrue(ops._is_valid_cache('db', 'table')) + ops.clear_con('table') + self.assertFalse(ops._is_valid_cache('db', 'table')) + + def test_valid_implementation(self): + # generic fills the cache on a per-table basis + ops = self.CacheOps() + self.assertFalse(ops._is_valid_cache('db', 'table')) + self.assertFalse(ops._is_valid_cache('db', 'other_table')) + ops.lookup_constraint('db', 'table') + self.assertTrue(ops._is_valid_cache('db', 'table')) + self.assertFalse(ops._is_valid_cache('db', 'other_table')) + ops.lookup_constraint('db', 'other_table') + self.assertTrue(ops._is_valid_cache('db', 'table')) + self.assertTrue(ops._is_valid_cache('db', 'other_table')) + ops.clear_con('table') + self.assertFalse(ops._is_valid_cache('db', 'table')) + self.assertTrue(ops._is_valid_cache('db', 'other_table')) + +if mysql: + class TestCacheMysql(TestCacheGeneric): + base_ops_cls = mysql.DatabaseOperations + + def test_valid_implementation(self): + # mysql fills the cache on a per-db basis + ops = self.CacheOps() + self.assertFalse(ops._is_valid_cache('db', 'table')) + self.assertFalse(ops._is_valid_cache('db', 'other_table')) + ops.lookup_constraint('db', 'table') + self.assertTrue(ops._is_valid_cache('db', 'table')) + self.assertTrue(ops._is_valid_cache('db', 'other_table')) + ops.lookup_constraint('db', 'other_table') + self.assertTrue(ops._is_valid_cache('db', 'table')) + self.assertTrue(ops._is_valid_cache('db', 'other_table')) + ops.clear_con('table') + self.assertFalse(ops._is_valid_cache('db', 'table')) + self.assertTrue(ops._is_valid_cache('db', 'other_table')) diff --git a/lib/python2.7/site-packages/south/tests/db_firebird.py b/lib/python2.7/site-packages/south/tests/db_firebird.py new file mode 100644 index 0000000..2b6bd53 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/db_firebird.py @@ -0,0 +1,39 @@ +from django.db import models + +from south.db import db +from south.tests import unittest, skipUnless + + +class FirebirdTests(unittest.TestCase): + + """ + Tests firebird related issues + """ + + def setUp(self): + print('=' * 80) + print('Begin Firebird test') + + def tearDown(self): + print('End Firebird test') + print('=' * 80) + + @skipUnless(db.backend_name == "firebird", "Firebird-only test") + def test_firebird_double_index_creation_1317(self): + """ + Tests foreign key creation, especially uppercase (see #61) + """ + Test = db.mock_model(model_name='Test', + db_table='test5a', + db_tablespace='', + pk_field_name='ID', + pk_field_type=models.AutoField, + pk_field_args=[] + ) + db.create_table("test5a", [('ID', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True))]) + db.create_table("test5b", [ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('UNIQUE', models.ForeignKey(Test)), + ]) + db.execute_deferred_sql() + diff --git a/lib/python2.7/site-packages/south/tests/db_mysql.py b/lib/python2.7/site-packages/south/tests/db_mysql.py new file mode 100644 index 0000000..e83596c --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/db_mysql.py @@ -0,0 +1,164 @@ +# Additional MySQL-specific tests +# Written by: F. Gabriel Gosselin <gabrielNOSPAM@evidens.ca> +# Based on tests by: aarranz +from south.tests import unittest, skipUnless + + +from south.db import db, generic, mysql +from django.db import connection, models + +from south.utils.py3 import with_metaclass + + +# A class decoration may be used in lieu of this when Python 2.5 is the +# minimum. +class TestMySQLOperationsMeta(type): + + def __new__(mcs, name, bases, dict_): + decorator = skipUnless(db.backend_name == "mysql", 'MySQL-specific tests') + + for key, method in dict_.items(): + if key.startswith('test'): + dict_[key] = decorator(method) + + return type.__new__(mcs, name, bases, dict_) + +class TestMySQLOperations(with_metaclass(TestMySQLOperationsMeta, unittest.TestCase)): + """MySQL-specific tests""" + + def setUp(self): + db.debug = False + db.clear_deferred_sql() + + def tearDown(self): + pass + + def _create_foreign_tables(self, main_name, reference_name): + # Create foreign table and model + Foreign = db.mock_model(model_name='Foreign', db_table=reference_name, + db_tablespace='', pk_field_name='id', + pk_field_type=models.AutoField, + pk_field_args=[]) + db.create_table(reference_name, [ + ('id', models.AutoField(primary_key=True)), + ]) + # Create table with foreign key + db.create_table(main_name, [ + ('id', models.AutoField(primary_key=True)), + ('foreign', models.ForeignKey(Foreign)), + ]) + return Foreign + + def test_constraint_references(self): + """Tests that referred table is reported accurately""" + main_table = 'test_cns_ref' + reference_table = 'test_cr_foreign' + db.start_transaction() + self._create_foreign_tables(main_table, reference_table) + db.execute_deferred_sql() + constraint = db._find_foreign_constraints(main_table, 'foreign_id')[0] + references = db._lookup_constraint_references(main_table, constraint) + self.assertEquals((reference_table, 'id'), references) + db.delete_table(main_table) + db.delete_table(reference_table) + + def test_reverse_column_constraint(self): + """Tests that referred column in a foreign key (ex. id) is found""" + main_table = 'test_reverse_ref' + reference_table = 'test_rr_foreign' + db.start_transaction() + self._create_foreign_tables(main_table, reference_table) + db.execute_deferred_sql() + inverse = db._lookup_reverse_constraint(reference_table, 'id') + (cname, rev_table, rev_column) = inverse[0] + self.assertEquals(main_table, rev_table) + self.assertEquals('foreign_id', rev_column) + db.delete_table(main_table) + db.delete_table(reference_table) + + def test_delete_fk_column(self): + main_table = 'test_drop_foreign' + ref_table = 'test_df_ref' + self._create_foreign_tables(main_table, ref_table) + db.execute_deferred_sql() + constraints = db._find_foreign_constraints(main_table, 'foreign_id') + self.assertEquals(len(constraints), 1) + db.delete_column(main_table, 'foreign_id') + constraints = db._find_foreign_constraints(main_table, 'foreign_id') + self.assertEquals(len(constraints), 0) + db.delete_table(main_table) + db.delete_table(ref_table) + + def test_rename_fk_column(self): + main_table = 'test_rename_foreign' + ref_table = 'test_rf_ref' + self._create_foreign_tables(main_table, ref_table) + db.execute_deferred_sql() + constraints = db._find_foreign_constraints(main_table, 'foreign_id') + self.assertEquals(len(constraints), 1) + db.rename_column(main_table, 'foreign_id', 'reference_id') + db.execute_deferred_sql() #Create constraints + constraints = db._find_foreign_constraints(main_table, 'reference_id') + self.assertEquals(len(constraints), 1) + db.delete_table(main_table) + db.delete_table(ref_table) + + def test_rename_fk_inbound(self): + """ + Tests that the column referred to by an external column can be renamed. + Edge case, but also useful as stepping stone to renaming tables. + """ + main_table = 'test_rename_fk_inbound' + ref_table = 'test_rfi_ref' + self._create_foreign_tables(main_table, ref_table) + db.execute_deferred_sql() + constraints = db._lookup_reverse_constraint(ref_table, 'id') + self.assertEquals(len(constraints), 1) + db.rename_column(ref_table, 'id', 'rfi_id') + db.execute_deferred_sql() #Create constraints + constraints = db._lookup_reverse_constraint(ref_table, 'rfi_id') + self.assertEquals(len(constraints), 1) + cname = db._find_foreign_constraints(main_table, 'foreign_id')[0] + (rtable, rcolumn) = db._lookup_constraint_references(main_table, cname) + self.assertEquals(rcolumn, 'rfi_id') + db.delete_table(main_table) + db.delete_table(ref_table) + + def test_rename_constrained_table(self): + """Renames a table with a foreign key column (towards another table)""" + main_table = 'test_rn_table' + ref_table = 'test_rt_ref' + renamed_table = 'test_renamed_table' + self._create_foreign_tables(main_table, ref_table) + db.execute_deferred_sql() + constraints = db._find_foreign_constraints(main_table, 'foreign_id') + self.assertEquals(len(constraints), 1) + db.rename_table(main_table, renamed_table) + db.execute_deferred_sql() #Create constraints + constraints = db._find_foreign_constraints(renamed_table, 'foreign_id') + self.assertEquals(len(constraints), 1) + (rtable, rcolumn) = db._lookup_constraint_references( + renamed_table, constraints[0]) + self.assertEquals(rcolumn, 'id') + db.delete_table(renamed_table) + db.delete_table(ref_table) + + def test_renamed_referenced_table(self): + """Rename a table referred to in a foreign key""" + main_table = 'test_rn_refd_table' + ref_table = 'test_rrt_ref' + renamed_table = 'test_renamed_ref' + self._create_foreign_tables(main_table, ref_table) + db.execute_deferred_sql() + constraints = db._lookup_reverse_constraint(ref_table) + self.assertEquals(len(constraints), 1) + db.rename_table(ref_table, renamed_table) + db.execute_deferred_sql() #Create constraints + constraints = db._find_foreign_constraints(main_table, 'foreign_id') + self.assertEquals(len(constraints), 1) + (rtable, rcolumn) = db._lookup_constraint_references( + main_table, constraints[0]) + self.assertEquals(renamed_table, rtable) + db.delete_table(main_table) + db.delete_table(renamed_table) + diff --git a/lib/python2.7/site-packages/south/tests/deps_a/__init__.py b/lib/python2.7/site-packages/south/tests/deps_a/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_a/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/deps_a/migrations/0001_a.py b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0001_a.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0001_a.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_a/migrations/0002_a.py b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0002_a.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0002_a.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_a/migrations/0003_a.py b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0003_a.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0003_a.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_a/migrations/0004_a.py b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0004_a.py new file mode 100644 index 0000000..e5c2977 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0004_a.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('deps_b', '0003_b')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_a/migrations/0005_a.py b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0005_a.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_a/migrations/0005_a.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_a/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/deps_a/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_a/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/deps_a/models.py b/lib/python2.7/site-packages/south/tests/deps_a/models.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_a/models.py diff --git a/lib/python2.7/site-packages/south/tests/deps_b/__init__.py b/lib/python2.7/site-packages/south/tests/deps_b/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_b/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/deps_b/migrations/0001_b.py b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0001_b.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0001_b.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_b/migrations/0002_b.py b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0002_b.py new file mode 100644 index 0000000..459ea5d --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0002_b.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('deps_a', '0002_a')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_b/migrations/0003_b.py b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0003_b.py new file mode 100644 index 0000000..1692888 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0003_b.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('deps_a', '0003_a')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_b/migrations/0004_b.py b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0004_b.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0004_b.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_b/migrations/0005_b.py b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0005_b.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_b/migrations/0005_b.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_b/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/deps_b/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_b/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/deps_b/models.py b/lib/python2.7/site-packages/south/tests/deps_b/models.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_b/models.py diff --git a/lib/python2.7/site-packages/south/tests/deps_c/__init__.py b/lib/python2.7/site-packages/south/tests/deps_c/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_c/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/deps_c/migrations/0001_c.py b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0001_c.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0001_c.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_c/migrations/0002_c.py b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0002_c.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0002_c.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_c/migrations/0003_c.py b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0003_c.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0003_c.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_c/migrations/0004_c.py b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0004_c.py new file mode 100644 index 0000000..d27ed3a --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0004_c.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_c/migrations/0005_c.py b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0005_c.py new file mode 100644 index 0000000..459ea5d --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_c/migrations/0005_c.py @@ -0,0 +1,13 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = [('deps_a', '0002_a')] + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/deps_c/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/deps_c/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_c/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/deps_c/models.py b/lib/python2.7/site-packages/south/tests/deps_c/models.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/deps_c/models.py diff --git a/lib/python2.7/site-packages/south/tests/emptyapp/__init__.py b/lib/python2.7/site-packages/south/tests/emptyapp/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/emptyapp/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/emptyapp/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/emptyapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/emptyapp/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/emptyapp/models.py b/lib/python2.7/site-packages/south/tests/emptyapp/models.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/emptyapp/models.py diff --git a/lib/python2.7/site-packages/south/tests/fakeapp/__init__.py b/lib/python2.7/site-packages/south/tests/fakeapp/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/fakeapp/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0001_spam.py b/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0001_spam.py new file mode 100644 index 0000000..9739648 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0001_spam.py @@ -0,0 +1,17 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + # Model 'Spam' + db.create_table("southtest_spam", ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('weight', models.FloatField()), + ('expires', models.DateTimeField()), + ('name', models.CharField(max_length=255)) + )) + + def backwards(self): + db.delete_table("southtest_spam") + diff --git a/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0002_eggs.py b/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0002_eggs.py new file mode 100644 index 0000000..3ec8399 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0002_eggs.py @@ -0,0 +1,20 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + + Spam = db.mock_model(model_name='Spam', db_table='southtest_spam', db_tablespace='', pk_field_name='id', pk_field_type=models.AutoField) + + db.create_table("southtest_eggs", ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('size', models.FloatField()), + ('quantity', models.IntegerField()), + ('spam', models.ForeignKey(Spam)), + )) + + def backwards(self): + + db.delete_table("southtest_eggs") + diff --git a/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0003_alter_spam.py b/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0003_alter_spam.py new file mode 100644 index 0000000..39126c2 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/fakeapp/migrations/0003_alter_spam.py @@ -0,0 +1,18 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + + db.alter_column("southtest_spam", 'weight', models.FloatField(null=True)) + + def backwards(self): + + db.alter_column("southtest_spam", 'weight', models.FloatField()) + + models = { + "fakeapp.bug135": { + 'date': ('models.DateTimeField', [], {'default': 'datetime.datetime(2009, 5, 6, 15, 33, 15, 780013)'}), + } + } diff --git a/lib/python2.7/site-packages/south/tests/fakeapp/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/fakeapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/fakeapp/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/fakeapp/models.py b/lib/python2.7/site-packages/south/tests/fakeapp/models.py new file mode 100644 index 0000000..cc39eb7 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/fakeapp/models.py @@ -0,0 +1,111 @@ +# -*- coding: UTF-8 -*- + +from django.db import models +from django.contrib.auth.models import User as UserAlias + +from south.modelsinspector import add_introspection_rules + +on_delete_is_available = hasattr(models, "PROTECT") # models here is django.db.models + +def default_func(): + return "yays" + +# An empty case. +class Other1(models.Model): pass + +# Another one +class Other3(models.Model): pass +def get_sentinel_object(): + """ + A function to return the object to be used in place of any deleted object, + when using the SET option for on_delete. + """ + # Create a new one, so we always have an instance to test with. Can't work! + return Other3() + +# Nastiness. +class HorribleModel(models.Model): + "A model to test the edge cases of model parsing" + + ZERO, ONE = 0, 1 + + # First, some nice fields + name = models.CharField(max_length=255) + short_name = models.CharField(max_length=50) + slug = models.SlugField(unique=True) + + # A ForeignKey, to a model above, and then below + o1 = models.ForeignKey(Other1) + o2 = models.ForeignKey('Other2') + + if on_delete_is_available: + o_set_null_on_delete = models.ForeignKey('Other3', null=True, on_delete=models.SET_NULL) + o_cascade_delete = models.ForeignKey('Other3', null=True, on_delete=models.CASCADE, related_name="cascademe") + o_protect = models.ForeignKey('Other3', null=True, on_delete=models.PROTECT, related_name="dontcascademe") + o_default_on_delete = models.ForeignKey('Other3', null=True, default=1, on_delete=models.SET_DEFAULT, related_name="setmedefault") + o_set_on_delete_function = models.ForeignKey('Other3', null=True, default=1, on_delete=models.SET(get_sentinel_object), related_name="setsentinel") + o_set_on_delete_value = models.ForeignKey('Other3', null=True, default=1, on_delete=models.SET(get_sentinel_object()), related_name="setsentinelwithactualvalue") # dubious case + o_no_action_on_delete = models.ForeignKey('Other3', null=True, default=1, on_delete=models.DO_NOTHING, related_name="deletemeatyourperil") + + + # Now to something outside + user = models.ForeignKey(UserAlias, related_name="horribles") + + # Unicode! + code = models.CharField(max_length=25, default="↑↑↓↓←→←→BA") + + # Odd defaults! + class_attr = models.IntegerField(default=ZERO) + func = models.CharField(max_length=25, default=default_func) + + # Time to get nasty. Define a non-field choices, and use it + choices = [('hello', '1'), ('world', '2')] + choiced = models.CharField(max_length=20, choices=choices) + + class Meta: + db_table = "my_fave" + verbose_name = "Dr. Strangelove," + \ + """or how I learned to stop worrying +and love the bomb""" + + # Now spread over multiple lines + multiline = \ + models.TextField( + ) + +# Special case. +class Other2(models.Model): + # Try loading a field without a newline after it (inspect hates this) + close_but_no_cigar = models.PositiveIntegerField(primary_key=True) + +class CustomField(models.IntegerField): + def __init__(self, an_other_model, **kwargs): + super(CustomField, self).__init__(**kwargs) + self.an_other_model = an_other_model + +add_introspection_rules([ + ( + [CustomField], + [], + {'an_other_model': ('an_other_model', {})}, + ), +], ['^south\.tests\.fakeapp\.models\.CustomField']) + +class BaseModel(models.Model): + pass + +class SubModel(BaseModel): + others = models.ManyToManyField(Other1) + custom = CustomField(Other2) + +class CircularA(models.Model): + c = models.ForeignKey('CircularC') + +class CircularB(models.Model): + a = models.ForeignKey(CircularA) + +class CircularC(models.Model): + b = models.ForeignKey(CircularB) + +class Recursive(models.Model): + self = models.ForeignKey('self') diff --git a/lib/python2.7/site-packages/south/tests/freezer.py b/lib/python2.7/site-packages/south/tests/freezer.py new file mode 100644 index 0000000..82c4402 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/freezer.py @@ -0,0 +1,15 @@ +from south.tests import unittest + +from south.creator.freezer import model_dependencies +from south.tests.fakeapp import models + +class TestFreezer(unittest.TestCase): + def test_dependencies(self): + self.assertEqual(set(model_dependencies(models.SubModel)), + set([models.BaseModel, models.Other1, models.Other2])) + + self.assertEqual(set(model_dependencies(models.CircularA)), + set([models.CircularA, models.CircularB, models.CircularC])) + + self.assertEqual(set(model_dependencies(models.Recursive)), + set([models.Recursive])) diff --git a/lib/python2.7/site-packages/south/tests/inspector.py b/lib/python2.7/site-packages/south/tests/inspector.py new file mode 100644 index 0000000..dcd6d57 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/inspector.py @@ -0,0 +1,109 @@ + +from south.tests import Monkeypatcher, skipUnless +from south.modelsinspector import (convert_on_delete_handler, get_value, + IsDefault, models, value_clean) + +from fakeapp.models import HorribleModel, get_sentinel_object + + +on_delete_is_available = hasattr(models, "PROTECT") # models here is django.db.models +skipUnlessOnDeleteAvailable = skipUnless(on_delete_is_available, "not testing on_delete -- not available on Django<1.3") + +class TestModelInspector(Monkeypatcher): + + """ + Tests if the various parts of the modelinspector work. + """ + + def test_get_value(self): + + # Let's start nicely. + name = HorribleModel._meta.get_field_by_name("name")[0] + slug = HorribleModel._meta.get_field_by_name("slug")[0] + user = HorribleModel._meta.get_field_by_name("user")[0] + + # Simple int retrieval + self.assertEqual( + get_value(name, ["max_length", {}]), + "255", + ) + + # Bool retrieval + self.assertEqual( + get_value(slug, ["unique", {}]), + "True", + ) + + # String retrieval + self.assertEqual( + get_value(user, ["rel.related_name", {}]), + "'horribles'", + ) + + # Default triggering + self.assertEqual( + get_value(slug, ["unique", {"default": False}]), + "True", + ) + self.assertRaises( + IsDefault, + get_value, + slug, + ["unique", {"default": True}], + ) + + @skipUnlessOnDeleteAvailable + def test_get_value_on_delete(self): + + # First validate the FK fields with on_delete options + o_set_null_on_delete = HorribleModel._meta.get_field_by_name("o_set_null_on_delete")[0] + o_cascade_delete = HorribleModel._meta.get_field_by_name("o_cascade_delete")[0] + o_protect = HorribleModel._meta.get_field_by_name("o_protect")[0] + o_default_on_delete = HorribleModel._meta.get_field_by_name("o_default_on_delete")[0] + o_set_on_delete_function = HorribleModel._meta.get_field_by_name("o_set_on_delete_function")[0] + o_set_on_delete_value = HorribleModel._meta.get_field_by_name("o_set_on_delete_value")[0] + o_no_action_on_delete = HorribleModel._meta.get_field_by_name("o_no_action_on_delete")[0] + # TODO this is repeated from the introspection_details in modelsinspector: + # better to refactor that so we can reference these settings, in case they + # must change at some point. + on_delete = ["rel.on_delete", {"default": models.CASCADE, "is_django_function": True, "converter": convert_on_delete_handler, }] + + # Foreign Key cascade update/delete + self.assertRaises( + IsDefault, + get_value, + o_cascade_delete, + on_delete, + ) + self.assertEqual( + get_value(o_protect, on_delete), + "models.PROTECT", + ) + self.assertEqual( + get_value(o_no_action_on_delete, on_delete), + "models.DO_NOTHING", + ) + self.assertEqual( + get_value(o_set_null_on_delete, on_delete), + "models.SET_NULL", + ) + self.assertEqual( + get_value(o_default_on_delete, on_delete), + "models.SET_DEFAULT", + ) + # For now o_set_on_delete raises, see modelsinspector.py + #self.assertEqual( + # get_value(o_set_on_delete_function, on_delete), + # "models.SET(get_sentinel_object)", + #) + self.assertRaises( + ValueError, + get_value, + o_set_on_delete_function, + on_delete, + ) + self.assertEqual( + get_value(o_set_on_delete_value, on_delete), + "models.SET(%s)" % value_clean(get_sentinel_object()), + ) +
\ No newline at end of file diff --git a/lib/python2.7/site-packages/south/tests/logger.py b/lib/python2.7/site-packages/south/tests/logger.py new file mode 100644 index 0000000..78d159d --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/logger.py @@ -0,0 +1,82 @@ +import io +import logging +import os +import tempfile +from south.tests import unittest +import sys + +from django.conf import settings +from django.db import connection, models + +from south.db import db +from south.logger import close_logger + +class TestLogger(unittest.TestCase): + + """ + Tests if the logging is working reasonably. Some tests ignored if you don't + have write permission to the disk. + """ + + def setUp(self): + db.debug = False + self.test_path = tempfile.mkstemp(suffix=".south.log")[1] + + def test_db_execute_logging_nofile(self): + "Does logging degrade nicely if SOUTH_LOGGING_ON not set?" + settings.SOUTH_LOGGING_ON = False # this needs to be set to False + # to avoid issues where other tests + # set this to True. settings is shared + # between these tests. + db.create_table("test9", [('email_confirmed', models.BooleanField(default=False))]) + + def test_db_execute_logging_off_with_basic_config(self): + """ + Does the south logger avoid outputing debug information with + south logging turned off and python logging configured with + a basic config?" + """ + settings.SOUTH_LOGGING_ON = False + + # Set root logger to capture WARNING and worse + logging_stream = io.StringIO() + logging.basicConfig(stream=logging_stream, level=logging.WARNING) + + db.create_table("test12", [('email_confirmed', models.BooleanField(default=False))]) + + # since south logging is off, and our root logger is at WARNING + # we should not find DEBUG info in the log + self.assertEqual(logging_stream.getvalue(), '') + + def test_db_execute_logging_validfile(self): + "Does logging work when passing in a valid file?" + settings.SOUTH_LOGGING_ON = True + settings.SOUTH_LOGGING_FILE = self.test_path + # Check to see if we can make the logfile + try: + fh = open(self.test_path, "w") + except IOError: + # Permission was denied, ignore the test. + return + else: + fh.close() + # Do an action which logs + db.create_table("test10", [('email_confirmed', models.BooleanField(default=False))]) + # Close the logged file + close_logger() + try: + os.remove(self.test_path) + except: + # It's a tempfile, it's not vital we remove it. + pass + + def test_db_execute_logging_missingfilename(self): + "Does logging raise an error if there is a missing filename?" + settings.SOUTH_LOGGING_ON = True + settings.SOUTH_LOGGING_FILE = None + self.assertRaises( + IOError, + db.create_table, + "test11", + [('email_confirmed', models.BooleanField(default=False))], + ) diff --git a/lib/python2.7/site-packages/south/tests/logic.py b/lib/python2.7/site-packages/south/tests/logic.py new file mode 100644 index 0000000..2b21cef --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/logic.py @@ -0,0 +1,902 @@ +from south.tests import unittest + +import datetime +import sys +try: + set # builtin, python >=2.6 +except NameError: + from sets import Set as set # in stdlib, python >=2.3 + +from south import exceptions +from south.migration import migrate_app +from south.migration.base import all_migrations, Migrations +from south.creator.changes import ManualChanges +from south.migration.utils import depends, flatten, get_app_label +from south.models import MigrationHistory +from south.tests import Monkeypatcher +from south.db import db + + + +class TestBrokenMigration(Monkeypatcher): + installed_apps = ["fakeapp", "otherfakeapp", "brokenapp"] + + def test_broken_dependencies(self): + self.assertRaises( + exceptions.DependsOnUnmigratedApplication, + Migrations.calculate_dependencies, + force=True, + ) + #depends_on_unknown = self.brokenapp['0002_depends_on_unknown'] + #self.assertRaises(exceptions.DependsOnUnknownMigration, + # depends_on_unknown.dependencies) + #depends_on_higher = self.brokenapp['0003_depends_on_higher'] + #self.assertRaises(exceptions.DependsOnHigherMigration, + # depends_on_higher.dependencies) + + +class TestMigration(Monkeypatcher): + installed_apps = ["fakeapp", "otherfakeapp"] + + def setUp(self): + super(TestMigration, self).setUp() + self.fakeapp = Migrations('fakeapp') + self.otherfakeapp = Migrations('otherfakeapp') + Migrations.calculate_dependencies(force=True) + + def test_str(self): + migrations = [str(m) for m in self.fakeapp] + self.assertEqual(['fakeapp:0001_spam', + 'fakeapp:0002_eggs', + 'fakeapp:0003_alter_spam'], + migrations) + + def test_repr(self): + migrations = [repr(m) for m in self.fakeapp] + self.assertEqual(['<Migration: fakeapp:0001_spam>', + '<Migration: fakeapp:0002_eggs>', + '<Migration: fakeapp:0003_alter_spam>'], + migrations) + + def test_app_label(self): + self.assertEqual(['fakeapp', 'fakeapp', 'fakeapp'], + [m.app_label() for m in self.fakeapp]) + + def test_name(self): + self.assertEqual(['0001_spam', '0002_eggs', '0003_alter_spam'], + [m.name() for m in self.fakeapp]) + + def test_full_name(self): + self.assertEqual(['fakeapp.migrations.0001_spam', + 'fakeapp.migrations.0002_eggs', + 'fakeapp.migrations.0003_alter_spam'], + [m.full_name() for m in self.fakeapp]) + + def test_migration(self): + # Can't use vanilla import, modules beginning with numbers aren't in grammar + M1 = __import__("fakeapp.migrations.0001_spam", {}, {}, ['Migration']).Migration + M2 = __import__("fakeapp.migrations.0002_eggs", {}, {}, ['Migration']).Migration + M3 = __import__("fakeapp.migrations.0003_alter_spam", {}, {}, ['Migration']).Migration + self.assertEqual([M1, M2, M3], + [m.migration().Migration for m in self.fakeapp]) + self.assertRaises(exceptions.UnknownMigration, + self.fakeapp['9999_unknown'].migration) + + def test_previous(self): + self.assertEqual([None, + self.fakeapp['0001_spam'], + self.fakeapp['0002_eggs']], + [m.previous() for m in self.fakeapp]) + + def test_dependencies(self): + "Test that the dependency detection works." + self.assertEqual([ + set([]), + set([self.fakeapp['0001_spam']]), + set([self.fakeapp['0002_eggs']]) + ], + [m.dependencies for m in self.fakeapp], + ) + self.assertEqual([ + set([self.fakeapp['0001_spam']]), + set([self.otherfakeapp['0001_first']]), + set([ + self.otherfakeapp['0002_second'], + self.fakeapp['0003_alter_spam'], + ]) + ], + [m.dependencies for m in self.otherfakeapp], + ) + + def test_forwards_plan(self): + self.assertEqual([ + [self.fakeapp['0001_spam']], + [ + self.fakeapp['0001_spam'], + self.fakeapp['0002_eggs'] + ], + [ + self.fakeapp['0001_spam'], + self.fakeapp['0002_eggs'], + self.fakeapp['0003_alter_spam'], + ] + ], + [m.forwards_plan() for m in self.fakeapp], + ) + self.assertEqual([ + [ + self.fakeapp['0001_spam'], + self.otherfakeapp['0001_first'] + ], + [ + self.fakeapp['0001_spam'], + self.otherfakeapp['0001_first'], + self.otherfakeapp['0002_second'] + ], + [ + self.fakeapp['0001_spam'], + self.otherfakeapp['0001_first'], + self.otherfakeapp['0002_second'], + self.fakeapp['0002_eggs'], + self.fakeapp['0003_alter_spam'], + self.otherfakeapp['0003_third'], + ] + ], + [m.forwards_plan() for m in self.otherfakeapp], + ) + + def test_is_before(self): + F1 = self.fakeapp['0001_spam'] + F2 = self.fakeapp['0002_eggs'] + F3 = self.fakeapp['0003_alter_spam'] + O1 = self.otherfakeapp['0001_first'] + O2 = self.otherfakeapp['0002_second'] + O3 = self.otherfakeapp['0003_third'] + self.assertTrue(F1.is_before(F2)) + self.assertTrue(F1.is_before(F3)) + self.assertTrue(F2.is_before(F3)) + self.assertEqual(O3.is_before(O1), False) + self.assertEqual(O3.is_before(O2), False) + self.assertEqual(O2.is_before(O2), False) + self.assertEqual(O2.is_before(O1), False) + self.assertEqual(F2.is_before(O1), None) + self.assertEqual(F2.is_before(O2), None) + self.assertEqual(F2.is_before(O3), None) + + +class TestMigrationDependencies(Monkeypatcher): + installed_apps = ['deps_a', 'deps_b', 'deps_c'] + + def setUp(self): + super(TestMigrationDependencies, self).setUp() + self.deps_a = Migrations('deps_a') + self.deps_b = Migrations('deps_b') + self.deps_c = Migrations('deps_c') + Migrations.calculate_dependencies(force=True) + + def test_dependencies(self): + self.assertEqual( + [ + set([]), + set([self.deps_a['0001_a']]), + set([self.deps_a['0002_a']]), + set([ + self.deps_a['0003_a'], + self.deps_b['0003_b'], + ]), + set([self.deps_a['0004_a']]), + ], + [m.dependencies for m in self.deps_a], + ) + self.assertEqual( + [ + set([]), + set([ + self.deps_b['0001_b'], + self.deps_a['0002_a'] + ]), + set([ + self.deps_b['0002_b'], + self.deps_a['0003_a'] + ]), + set([self.deps_b['0003_b']]), + set([self.deps_b['0004_b']]), + ], + [m.dependencies for m in self.deps_b], + ) + self.assertEqual( + [ + set([]), + set([self.deps_c['0001_c']]), + set([self.deps_c['0002_c']]), + set([self.deps_c['0003_c']]), + set([ + self.deps_c['0004_c'], + self.deps_a['0002_a'] + ]), + ], + [m.dependencies for m in self.deps_c], + ) + + def test_dependents(self): + self.assertEqual([set([self.deps_a['0002_a']]), + set([self.deps_c['0005_c'], + self.deps_b['0002_b'], + self.deps_a['0003_a']]), + set([self.deps_b['0003_b'], + self.deps_a['0004_a']]), + set([self.deps_a['0005_a']]), + set([])], + [m.dependents for m in self.deps_a]) + self.assertEqual([set([self.deps_b['0002_b']]), + set([self.deps_b['0003_b']]), + set([self.deps_b['0004_b'], + self.deps_a['0004_a']]), + set([self.deps_b['0005_b']]), + set([])], + [m.dependents for m in self.deps_b]) + self.assertEqual([set([self.deps_c['0002_c']]), + set([self.deps_c['0003_c']]), + set([self.deps_c['0004_c']]), + set([self.deps_c['0005_c']]), + set([])], + [m.dependents for m in self.deps_c]) + + def test_forwards_plan(self): + self.assertEqual([[self.deps_a['0001_a']], + [self.deps_a['0001_a'], + self.deps_a['0002_a']], + [self.deps_a['0001_a'], + self.deps_a['0002_a'], + self.deps_a['0003_a']], + [self.deps_b['0001_b'], + self.deps_a['0001_a'], + self.deps_a['0002_a'], + self.deps_b['0002_b'], + self.deps_a['0003_a'], + self.deps_b['0003_b'], + self.deps_a['0004_a']], + [self.deps_b['0001_b'], + self.deps_a['0001_a'], + self.deps_a['0002_a'], + self.deps_b['0002_b'], + self.deps_a['0003_a'], + self.deps_b['0003_b'], + self.deps_a['0004_a'], + self.deps_a['0005_a']]], + [m.forwards_plan() for m in self.deps_a]) + self.assertEqual([[self.deps_b['0001_b']], + [self.deps_b['0001_b'], + self.deps_a['0001_a'], + self.deps_a['0002_a'], + self.deps_b['0002_b']], + [self.deps_b['0001_b'], + self.deps_a['0001_a'], + self.deps_a['0002_a'], + self.deps_b['0002_b'], + self.deps_a['0003_a'], + self.deps_b['0003_b']], + [self.deps_b['0001_b'], + self.deps_a['0001_a'], + self.deps_a['0002_a'], + self.deps_b['0002_b'], + self.deps_a['0003_a'], + self.deps_b['0003_b'], + self.deps_b['0004_b']], + [self.deps_b['0001_b'], + self.deps_a['0001_a'], + self.deps_a['0002_a'], + self.deps_b['0002_b'], + self.deps_a['0003_a'], + self.deps_b['0003_b'], + self.deps_b['0004_b'], + self.deps_b['0005_b']]], + [m.forwards_plan() for m in self.deps_b]) + self.assertEqual([[self.deps_c['0001_c']], + [self.deps_c['0001_c'], + self.deps_c['0002_c']], + [self.deps_c['0001_c'], + self.deps_c['0002_c'], + self.deps_c['0003_c']], + [self.deps_c['0001_c'], + self.deps_c['0002_c'], + self.deps_c['0003_c'], + self.deps_c['0004_c']], + [self.deps_c['0001_c'], + self.deps_c['0002_c'], + self.deps_c['0003_c'], + self.deps_c['0004_c'], + self.deps_a['0001_a'], + self.deps_a['0002_a'], + self.deps_c['0005_c']]], + [m.forwards_plan() for m in self.deps_c]) + + def test_backwards_plan(self): + self.assertEqual([ + [ + self.deps_c['0005_c'], + self.deps_b['0005_b'], + self.deps_b['0004_b'], + self.deps_a['0005_a'], + self.deps_a['0004_a'], + self.deps_b['0003_b'], + self.deps_b['0002_b'], + self.deps_a['0003_a'], + self.deps_a['0002_a'], + self.deps_a['0001_a'], + ], + [ + self.deps_c['0005_c'], + self.deps_b['0005_b'], + self.deps_b['0004_b'], + self.deps_a['0005_a'], + self.deps_a['0004_a'], + self.deps_b['0003_b'], + self.deps_b['0002_b'], + self.deps_a['0003_a'], + self.deps_a['0002_a'], + ], + [ + self.deps_b['0005_b'], + self.deps_b['0004_b'], + self.deps_a['0005_a'], + self.deps_a['0004_a'], + self.deps_b['0003_b'], + self.deps_a['0003_a'], + ], + [ + self.deps_a['0005_a'], + self.deps_a['0004_a'], + ], + [ + self.deps_a['0005_a'], + ] + ], [m.backwards_plan() for m in self.deps_a]) + self.assertEqual([ + [ + self.deps_b['0005_b'], + self.deps_b['0004_b'], + self.deps_a['0005_a'], + self.deps_a['0004_a'], + self.deps_b['0003_b'], + self.deps_b['0002_b'], + self.deps_b['0001_b'], + ], + [ + self.deps_b['0005_b'], + self.deps_b['0004_b'], + self.deps_a['0005_a'], + self.deps_a['0004_a'], + self.deps_b['0003_b'], + self.deps_b['0002_b'], + ], + [ + self.deps_b['0005_b'], + self.deps_b['0004_b'], + self.deps_a['0005_a'], + self.deps_a['0004_a'], + self.deps_b['0003_b'], + ], + [ + self.deps_b['0005_b'], + self.deps_b['0004_b'], + ], + [ + self.deps_b['0005_b'], + ], + ], [m.backwards_plan() for m in self.deps_b]) + self.assertEqual([ + [ + self.deps_c['0005_c'], + self.deps_c['0004_c'], + self.deps_c['0003_c'], + self.deps_c['0002_c'], + self.deps_c['0001_c'], + ], + [ + self.deps_c['0005_c'], + self.deps_c['0004_c'], + self.deps_c['0003_c'], + self.deps_c['0002_c'], + ], + [ + self.deps_c['0005_c'], + self.deps_c['0004_c'], + self.deps_c['0003_c'], + ], + [ + self.deps_c['0005_c'], + self.deps_c['0004_c'], + ], + [self.deps_c['0005_c']] + ], [m.backwards_plan() for m in self.deps_c]) + + +class TestCircularDependencies(Monkeypatcher): + installed_apps = ["circular_a", "circular_b"] + + def test_plans(self): + Migrations.calculate_dependencies(force=True) + circular_a = Migrations('circular_a') + circular_b = Migrations('circular_b') + self.assertRaises( + exceptions.CircularDependency, + circular_a[-1].forwards_plan, + ) + self.assertRaises( + exceptions.CircularDependency, + circular_b[-1].forwards_plan, + ) + self.assertRaises( + exceptions.CircularDependency, + circular_a[-1].backwards_plan, + ) + self.assertRaises( + exceptions.CircularDependency, + circular_b[-1].backwards_plan, + ) + + +class TestMigrations(Monkeypatcher): + installed_apps = ["fakeapp", "otherfakeapp"] + + def test_all(self): + + M1 = Migrations(__import__("fakeapp", {}, {}, [''])) + M2 = Migrations(__import__("otherfakeapp", {}, {}, [''])) + + self.assertEqual( + [M1, M2], + list(all_migrations()), + ) + + def test(self): + + M1 = Migrations(__import__("fakeapp", {}, {}, [''])) + + self.assertEqual(M1, Migrations("fakeapp")) + self.assertEqual(M1, Migrations(self.create_fake_app("fakeapp"))) + + def test_application(self): + fakeapp = Migrations("fakeapp") + application = __import__("fakeapp", {}, {}, ['']) + self.assertEqual(application, fakeapp.application) + + def test_migration(self): + # Can't use vanilla import, modules beginning with numbers aren't in grammar + M1 = __import__("fakeapp.migrations.0001_spam", {}, {}, ['Migration']).Migration + M2 = __import__("fakeapp.migrations.0002_eggs", {}, {}, ['Migration']).Migration + migration = Migrations('fakeapp') + self.assertEqual(M1, migration['0001_spam'].migration().Migration) + self.assertEqual(M2, migration['0002_eggs'].migration().Migration) + self.assertRaises(exceptions.UnknownMigration, + migration['0001_jam'].migration) + + def test_guess_migration(self): + # Can't use vanilla import, modules beginning with numbers aren't in grammar + M1 = __import__("fakeapp.migrations.0001_spam", {}, {}, ['Migration']).Migration + migration = Migrations('fakeapp') + self.assertEqual(M1, migration.guess_migration("0001_spam").migration().Migration) + self.assertEqual(M1, migration.guess_migration("0001_spa").migration().Migration) + self.assertEqual(M1, migration.guess_migration("0001_sp").migration().Migration) + self.assertEqual(M1, migration.guess_migration("0001_s").migration().Migration) + self.assertEqual(M1, migration.guess_migration("0001_").migration().Migration) + self.assertEqual(M1, migration.guess_migration("0001").migration().Migration) + self.assertRaises(exceptions.UnknownMigration, + migration.guess_migration, "0001-spam") + self.assertRaises(exceptions.MultiplePrefixMatches, + migration.guess_migration, "000") + self.assertRaises(exceptions.MultiplePrefixMatches, + migration.guess_migration, "") + self.assertRaises(exceptions.UnknownMigration, + migration.guess_migration, "0001_spams") + self.assertRaises(exceptions.UnknownMigration, + migration.guess_migration, "0001_jam") + + def test_app_label(self): + names = ['fakeapp', 'otherfakeapp'] + self.assertEqual(names, + [Migrations(n).app_label() for n in names]) + + def test_full_name(self): + names = ['fakeapp', 'otherfakeapp'] + self.assertEqual([n + '.migrations' for n in names], + [Migrations(n).full_name() for n in names]) + + +class TestMigrationLogic(Monkeypatcher): + + """ + Tests if the various logic functions in migration actually work. + """ + + installed_apps = ["fakeapp", "otherfakeapp"] + + def setUp(self): + super(TestMigrationLogic, self).setUp() + MigrationHistory.objects.all().delete() + + def assertListEqual(self, list1, list2, msg=None): + list1 = set(list1) + list2 = set(list2) + return self.assert_(list1 == list2, "%s is not equal to %s" % (list1, list2)) + + def test_find_ghost_migrations(self): + pass + + def test_apply_migrations(self): + migrations = Migrations("fakeapp") + + # We should start with no migrations + self.assertEqual(list(MigrationHistory.objects.all()), []) + + # Apply them normally + migrate_app(migrations, target_name=None, fake=False, + load_initial_data=True) + + # We should finish with all migrations + self.assertListEqual( + (("fakeapp", "0001_spam"), + ("fakeapp", "0002_eggs"), + ("fakeapp", "0003_alter_spam"),), + MigrationHistory.objects.values_list("app_name", "migration"), + ) + + # Now roll them backwards + migrate_app(migrations, target_name="zero", fake=False) + + # Finish with none + self.assertEqual(list(MigrationHistory.objects.all()), []) + + + def test_migration_merge_forwards(self): + migrations = Migrations("fakeapp") + + # We should start with no migrations + self.assertEqual(list(MigrationHistory.objects.all()), []) + + # Insert one in the wrong order + MigrationHistory.objects.create(app_name = "fakeapp", + migration = "0002_eggs", + applied = datetime.datetime.now()) + + # Did it go in? + self.assertListEqual( + (("fakeapp", "0002_eggs"),), + MigrationHistory.objects.values_list("app_name", "migration"), + ) + + # Apply them normally + self.assertRaises(exceptions.InconsistentMigrationHistory, + migrate_app, + migrations, target_name=None, fake=False) + self.assertRaises(exceptions.InconsistentMigrationHistory, + migrate_app, + migrations, target_name='zero', fake=False) + try: + migrate_app(migrations, target_name=None, fake=False) + except exceptions.InconsistentMigrationHistory as e: + self.assertEqual( + [ + ( + migrations['0002_eggs'], + migrations['0001_spam'], + ) + ], + e.problems, + ) + try: + migrate_app(migrations, target_name="zero", fake=False) + except exceptions.InconsistentMigrationHistory as e: + self.assertEqual( + [ + ( + migrations['0002_eggs'], + migrations['0001_spam'], + ) + ], + e.problems, + ) + + # Nothing should have changed (no merge mode!) + self.assertListEqual( + (("fakeapp", "0002_eggs"),), + MigrationHistory.objects.values_list("app_name", "migration"), + ) + + # Apply with merge + migrate_app(migrations, target_name=None, merge=True, fake=False) + + # We should finish with all migrations + self.assertListEqual( + (("fakeapp", "0001_spam"), + ("fakeapp", "0002_eggs"), + ("fakeapp", "0003_alter_spam"),), + MigrationHistory.objects.values_list("app_name", "migration"), + ) + + # Now roll them backwards + migrate_app(migrations, target_name="0002", fake=False) + migrate_app(migrations, target_name="0001", fake=True) + migrate_app(migrations, target_name="zero", fake=False) + + # Finish with none + self.assertEqual(list(MigrationHistory.objects.all()), []) + + def test_alter_column_null(self): + + def null_ok(eat_exception=True): + from django.db import connection, transaction + # the DBAPI introspection module fails on postgres NULLs. + cursor = connection.cursor() + + # SQLite has weird now() + if db.backend_name == "sqlite3": + now_func = "DATETIME('NOW')" + # So does SQLServer... should we be using a backend attribute? + elif db.backend_name == "pyodbc": + now_func = "GETDATE()" + elif db.backend_name == "oracle": + now_func = "SYSDATE" + else: + now_func = "NOW()" + + try: + if db.backend_name == "pyodbc": + cursor.execute("SET IDENTITY_INSERT southtest_spam ON;") + cursor.execute("INSERT INTO southtest_spam (id, weight, expires, name) VALUES (100, NULL, %s, 'whatever');" % now_func) + except: + if eat_exception: + transaction.rollback() + return False + else: + raise + else: + cursor.execute("DELETE FROM southtest_spam") + transaction.commit() + return True + + MigrationHistory.objects.all().delete() + migrations = Migrations("fakeapp") + + # by default name is NOT NULL + migrate_app(migrations, target_name="0002", fake=False) + self.failIf(null_ok()) + self.assertListEqual( + (("fakeapp", "0001_spam"), + ("fakeapp", "0002_eggs"),), + MigrationHistory.objects.values_list("app_name", "migration"), + ) + + # after 0003, it should be NULL + migrate_app(migrations, target_name="0003", fake=False) + self.assert_(null_ok(False)) + self.assertListEqual( + (("fakeapp", "0001_spam"), + ("fakeapp", "0002_eggs"), + ("fakeapp", "0003_alter_spam"),), + MigrationHistory.objects.values_list("app_name", "migration"), + ) + + # make sure it is NOT NULL again + migrate_app(migrations, target_name="0002", fake=False) + self.failIf(null_ok(), 'weight not null after migration') + self.assertListEqual( + (("fakeapp", "0001_spam"), + ("fakeapp", "0002_eggs"),), + MigrationHistory.objects.values_list("app_name", "migration"), + ) + + # finish with no migrations, otherwise other tests fail... + migrate_app(migrations, target_name="zero", fake=False) + self.assertEqual(list(MigrationHistory.objects.all()), []) + + def test_dependencies(self): + + fakeapp = Migrations("fakeapp") + otherfakeapp = Migrations("otherfakeapp") + + # Test a simple path + self.assertEqual([fakeapp['0001_spam'], + fakeapp['0002_eggs'], + fakeapp['0003_alter_spam']], + fakeapp['0003_alter_spam'].forwards_plan()) + + # And a complex one. + self.assertEqual( + [ + fakeapp['0001_spam'], + otherfakeapp['0001_first'], + otherfakeapp['0002_second'], + fakeapp['0002_eggs'], + fakeapp['0003_alter_spam'], + otherfakeapp['0003_third'] + ], + otherfakeapp['0003_third'].forwards_plan(), + ) + + +class TestMigrationUtils(Monkeypatcher): + installed_apps = ["fakeapp", "otherfakeapp"] + + def test_get_app_label(self): + self.assertEqual( + "southtest", + get_app_label(self.create_fake_app("southtest.models")), + ) + self.assertEqual( + "baz", + get_app_label(self.create_fake_app("foo.bar.baz.models")), + ) + +class TestUtils(unittest.TestCase): + + def test_flatten(self): + self.assertEqual([], list(flatten(iter([])))) + self.assertEqual([], list(flatten(iter([iter([]), ])))) + self.assertEqual([1], list(flatten(iter([1])))) + self.assertEqual([1, 2], list(flatten(iter([1, 2])))) + self.assertEqual([1, 2], list(flatten(iter([iter([1]), 2])))) + self.assertEqual([1, 2], list(flatten(iter([iter([1, 2])])))) + self.assertEqual([1, 2, 3], list(flatten(iter([iter([1, 2]), 3])))) + self.assertEqual([1, 2, 3], + list(flatten(iter([iter([1]), iter([2]), 3])))) + self.assertEqual([1, 2, 3], + list(flatten([[1], [2], 3]))) + + def test_depends(self): + graph = {'A1': []} + self.assertEqual(['A1'], + depends('A1', lambda n: graph[n])) + graph = {'A1': [], + 'A2': ['A1'], + 'A3': ['A2']} + self.assertEqual(['A1', 'A2', 'A3'], + depends('A3', lambda n: graph[n])) + graph = {'A1': [], + 'A2': ['A1'], + 'A3': ['A2', 'A1']} + self.assertEqual(['A1', 'A2', 'A3'], + depends('A3', lambda n: graph[n])) + graph = {'A1': [], + 'A2': ['A1'], + 'A3': ['A2', 'A1', 'B1'], + 'B1': []} + self.assertEqual( + ['B1', 'A1', 'A2', 'A3'], + depends('A3', lambda n: graph[n]), + ) + graph = {'A1': [], + 'A2': ['A1'], + 'A3': ['A2', 'A1', 'B2'], + 'B1': [], + 'B2': ['B1']} + self.assertEqual( + ['B1', 'B2', 'A1', 'A2', 'A3'], + depends('A3', lambda n: graph[n]), + ) + graph = {'A1': [], + 'A2': ['A1', 'B1'], + 'A3': ['A2'], + 'B1': ['A1']} + self.assertEqual(['A1', 'B1', 'A2', 'A3'], + depends('A3', lambda n: graph[n])) + graph = {'A1': [], + 'A2': ['A1'], + 'A3': ['A2', 'A1', 'B2'], + 'B1': [], + 'B2': ['B1', 'C1'], + 'C1': ['B1']} + self.assertEqual( + ['B1', 'C1', 'B2', 'A1', 'A2', 'A3'], + depends('A3', lambda n: graph[n]), + ) + graph = {'A1': [], + 'A2': ['A1'], + 'A3': ['A2', 'B2', 'A1', 'C1'], + 'B1': ['A1'], + 'B2': ['B1', 'C2', 'A1'], + 'C1': ['B1'], + 'C2': ['C1', 'A1'], + 'C3': ['C2']} + self.assertEqual( + ['A1', 'B1', 'C1', 'C2', 'B2', 'A2', 'A3'], + depends('A3', lambda n: graph[n]), + ) + + def assertCircularDependency(self, trace, target, graph): + "Custom assertion that checks a circular dependency is detected correctly." + self.assertRaises( + exceptions.CircularDependency, + depends, + target, + lambda n: graph[n], + ) + try: + depends(target, lambda n: graph[n]) + except exceptions.CircularDependency as e: + self.assertEqual(trace, e.trace) + + def test_depends_cycle(self): + graph = {'A1': ['A1']} + self.assertCircularDependency( + ['A1', 'A1'], + 'A1', + graph, + ) + graph = {'A1': [], + 'A2': ['A1', 'A2'], + 'A3': ['A2']} + self.assertCircularDependency( + ['A2', 'A2'], + 'A3', + graph, + ) + graph = {'A1': [], + 'A2': ['A1'], + 'A3': ['A2', 'A3'], + 'A4': ['A3']} + self.assertCircularDependency( + ['A3', 'A3'], + 'A4', + graph, + ) + graph = {'A1': ['B1'], + 'B1': ['A1']} + self.assertCircularDependency( + ['A1', 'B1', 'A1'], + 'A1', + graph, + ) + graph = {'A1': [], + 'A2': ['A1', 'B2'], + 'A3': ['A2'], + 'B1': [], + 'B2': ['B1', 'A2'], + 'B3': ['B2']} + self.assertCircularDependency( + ['A2', 'B2', 'A2'], + 'A3', + graph, + ) + graph = {'A1': [], + 'A2': ['A1', 'B3'], + 'A3': ['A2'], + 'B1': [], + 'B2': ['B1', 'A2'], + 'B3': ['B2']} + self.assertCircularDependency( + ['A2', 'B3', 'B2', 'A2'], + 'A3', + graph, + ) + graph = {'A1': [], + 'A2': ['A1'], + 'A3': ['A2', 'B2'], + 'A4': ['A3'], + 'B1': ['A3'], + 'B2': ['B1']} + self.assertCircularDependency( + ['A3', 'B2', 'B1', 'A3'], + 'A4', + graph, + ) + +class TestManualChanges(Monkeypatcher): + installed_apps = ["fakeapp", "otherfakeapp"] + + def test_suggest_name(self): + migrations = Migrations('fakeapp') + change = ManualChanges(migrations, + [], + ['fakeapp.slug'], + []) + self.assertEquals(change.suggest_name(), + 'add_field_fakeapp_slug') + + change = ManualChanges(migrations, + [], + [], + ['fakeapp.slug']) + self.assertEquals(change.suggest_name(), + 'add_index_fakeapp_slug') diff --git a/lib/python2.7/site-packages/south/tests/non_managed/__init__.py b/lib/python2.7/site-packages/south/tests/non_managed/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/non_managed/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/non_managed/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/non_managed/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/non_managed/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/non_managed/models.py b/lib/python2.7/site-packages/south/tests/non_managed/models.py new file mode 100644 index 0000000..e520d94 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/non_managed/models.py @@ -0,0 +1,16 @@ +# -*- coding: UTF-8 -*- + +""" +An app with a model that is not managed for testing that South does +not try to manage it in any way +""" +from django.db import models + +class Legacy(models.Model): + + name = models.CharField(max_length=10) + size = models.IntegerField() + + class Meta: + db_table = "legacy_table" + managed = False diff --git a/lib/python2.7/site-packages/south/tests/otherfakeapp/__init__.py b/lib/python2.7/site-packages/south/tests/otherfakeapp/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/otherfakeapp/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0001_first.py b/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0001_first.py new file mode 100644 index 0000000..ad9c095 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0001_first.py @@ -0,0 +1,15 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = ( + ("fakeapp", "0001_spam"), + ) + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0002_second.py b/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0002_second.py new file mode 100644 index 0000000..7c0fb0c --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0002_second.py @@ -0,0 +1,11 @@ +from south.db import db +from django.db import models + +class Migration: + + def forwards(self): + pass + + def backwards(self): + pass + diff --git a/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0003_third.py b/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0003_third.py new file mode 100644 index 0000000..fa8ed97 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/0003_third.py @@ -0,0 +1,14 @@ +from south.db import db +from django.db import models + +class Migration: + + depends_on = ( + ("fakeapp", "0003_alter_spam"), + ) + + def forwards(self): + pass + + def backwards(self): + pass diff --git a/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/__init__.py b/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/otherfakeapp/migrations/__init__.py diff --git a/lib/python2.7/site-packages/south/tests/otherfakeapp/models.py b/lib/python2.7/site-packages/south/tests/otherfakeapp/models.py new file mode 100644 index 0000000..93a4b8e --- /dev/null +++ b/lib/python2.7/site-packages/south/tests/otherfakeapp/models.py @@ -0,0 +1 @@ +# This file left intentionally blank.
\ No newline at end of file |