diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 9678026c79..a38ab0ed1d 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -1,5 +1,6 @@ from django.db import models from django.db.models.loading import BaseAppCache +from django.utils.module_loading import import_by_path class ProjectState(object): @@ -15,12 +16,9 @@ class ProjectState(object): def clone(self): "Returns an exact copy of this ProjectState" - ps = ProjectState( + return ProjectState( models = dict((k, v.copy()) for k, v in self.models.items()) ) - for model in ps.models.values(): - model.project_state = ps - return ps def render(self): "Turns the project state into actual models in a new AppCache" @@ -33,8 +31,11 @@ class ProjectState(object): @classmethod def from_app_cache(cls, app_cache): "Takes in an AppCache and returns a ProjectState matching it" + models = {} for model in app_cache.get_models(): - print model + model_state = ModelState.from_model(model) + models[(model_state.app_label, model_state.name.lower())] = model_state + return cls(models) class ModelState(object): @@ -44,18 +45,36 @@ class ModelState(object): mutate this one and then render it into a Model as required. """ - def __init__(self, project_state, app_label, name, fields=None, options=None, bases=None): - self.project_state = project_state + def __init__(self, app_label, name, fields=None, options=None, bases=None): self.app_label = app_label self.name = name self.fields = fields or [] self.options = options or {} - self.bases = bases or None + self.bases = bases or (models.Model, ) + + @classmethod + def from_model(cls, model): + """ + Feed me a model, get a ModelState representing it out. + """ + # Deconstruct the fields + fields = [] + for field in model._meta.local_fields: + name, path, args, kwargs = field.deconstruct() + field_class = import_by_path(path) + fields.append((name, field_class(*args, **kwargs))) + # Make our record + return cls( + model._meta.app_label, + model._meta.object_name, + fields, + {}, + None, + ) def clone(self): "Returns an exact copy of this ModelState" return self.__class__( - project_state = self.project_state, app_label = self.app_label, name = self.name, fields = self.fields, diff --git a/tests/migrations/tests.py b/tests/migrations/test_graph.py similarity index 88% rename from tests/migrations/tests.py rename to tests/migrations/test_graph.py index 9ef5e37b8f..b35d04fb8a 100644 --- a/tests/migrations/tests.py +++ b/tests/migrations/test_graph.py @@ -134,29 +134,3 @@ class LoaderTests(TransactionTestCase): graph.forwards_plan(("migrations", "0002_second")), [("migrations", "0001_initial"), ("migrations", "0002_second")], ) - - -class RecorderTests(TestCase): - """ - Tests the disk and database loader. - """ - - def test_apply(self): - """ - Tests marking migrations as applied/unapplied. - """ - recorder = MigrationRecorder(connection) - self.assertEqual( - recorder.applied_migrations(), - set(), - ) - recorder.record_applied("myapp", "0432_ponies") - self.assertEqual( - recorder.applied_migrations(), - set([("myapp", "0432_ponies")]), - ) - recorder.record_unapplied("myapp", "0432_ponies") - self.assertEqual( - recorder.applied_migrations(), - set(), - ) diff --git a/tests/migrations/test_loader.py b/tests/migrations/test_loader.py new file mode 100644 index 0000000000..f8f31734f1 --- /dev/null +++ b/tests/migrations/test_loader.py @@ -0,0 +1,29 @@ +from django.test import TestCase +from django.db import connection +from django.db.migrations.recorder import MigrationRecorder + + +class RecorderTests(TestCase): + """ + Tests the disk and database loader. + """ + + def test_apply(self): + """ + Tests marking migrations as applied/unapplied. + """ + recorder = MigrationRecorder(connection) + self.assertEqual( + recorder.applied_migrations(), + set(), + ) + recorder.record_applied("myapp", "0432_ponies") + self.assertEqual( + recorder.applied_migrations(), + set([("myapp", "0432_ponies")]), + ) + recorder.record_unapplied("myapp", "0432_ponies") + self.assertEqual( + recorder.applied_migrations(), + set(), + ) diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py new file mode 100644 index 0000000000..72a259bb24 --- /dev/null +++ b/tests/migrations/test_state.py @@ -0,0 +1,43 @@ +from django.test import TestCase +from django.db import models +from django.db.models.loading import BaseAppCache +from django.db.migrations.state import ProjectState + + +class StateTests(TestCase): + """ + Tests state construction, rendering and modification by operations. + """ + + def test_create(self): + """ + Tests making a ProjectState from an AppCache + """ + new_app_cache = BaseAppCache() + + class Author(models.Model): + name = models.CharField(max_length=255) + bio = models.TextField() + age = models.IntegerField(blank=True, null=True) + class Meta: + app_label = "migrations" + app_cache = new_app_cache + + class Book(models.Model): + title = models.CharField(max_length=1000) + author = models.ForeignKey(Author) + class Meta: + app_label = "migrations" + app_cache = new_app_cache + + project_state = ProjectState.from_app_cache(new_app_cache) + author_state = project_state.models['migrations', 'author'] + book_state = project_state.models['migrations', 'book'] + + self.assertEqual(author_state.app_label, "migrations") + self.assertEqual(author_state.name, "Author") + self.assertEqual([x for x, y in author_state.fields], ["id", "name", "bio", "age"]) + self.assertEqual(author_state.fields[1][1].max_length, 255) + self.assertEqual(author_state.fields[2][1].null, False) + self.assertEqual(author_state.fields[3][1].null, True) + self.assertEqual(author_state.bases, (models.Model, ))