From 76d93a52cd56be23104f824e6755ecc8d3a34d94 Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Fri, 10 May 2013 17:07:13 +0100 Subject: [PATCH] Make a start on operations and state (not sure if final layout) --- django/db/migrations/operations/__init__.py | 1 + django/db/migrations/operations/base.py | 38 ++++++++++ django/db/migrations/operations/models.py | 26 +++++++ django/db/migrations/state.py | 81 +++++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 django/db/migrations/operations/__init__.py create mode 100644 django/db/migrations/operations/base.py create mode 100644 django/db/migrations/operations/models.py create mode 100644 django/db/migrations/state.py diff --git a/django/db/migrations/operations/__init__.py b/django/db/migrations/operations/__init__.py new file mode 100644 index 0000000000..4fb70b0418 --- /dev/null +++ b/django/db/migrations/operations/__init__.py @@ -0,0 +1 @@ +from .models import CreateModel, DeleteModel diff --git a/django/db/migrations/operations/base.py b/django/db/migrations/operations/base.py new file mode 100644 index 0000000000..b24b45a09a --- /dev/null +++ b/django/db/migrations/operations/base.py @@ -0,0 +1,38 @@ +class Operation(object): + """ + Base class for migration operations. + + It's responsible for both mutating the in-memory model state + (see db/migrations/state.py) to represent what it performs, as well + as actually performing it against a live database. + + Note that some operations won't modify memory state at all (e.g. data + copying operations), and some will need their modifications to be + optionally specified by the user (e.g. custom Python code snippets) + """ + + # If this migration can be run in reverse. + # Some operations are impossible to reverse, like deleting data. + reversible = True + + def state_forwards(self, app, state): + """ + Takes the state from the previous migration, and mutates it + so that it matches what this migration would perform. + """ + raise NotImplementedError() + + def database_forwards(self, app, schema_editor, from_state, to_state): + """ + Performs the mutation on the database schema in the normal + (forwards) direction. + """ + raise NotImplementedError() + + def database_backwards(self, app, schema_editor, from_state, to_state): + """ + Performs the mutation on the database schema in the reverse + direction - e.g. if this were CreateModel, it would in fact + drop the model's table. + """ + raise NotImplementedError() diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py new file mode 100644 index 0000000000..fd709e26fa --- /dev/null +++ b/django/db/migrations/operations/models.py @@ -0,0 +1,26 @@ +from .base import Operation +from django.db.migrations.state import ModelState + + +class CreateModel(Operation): + """ + Create a model's table. + """ + + def __init__(self, name): + self.name = name + + def state_forwards(self, app, state): + state.models[app, self.name.lower()] = ModelState(state, app, self.name) + + def database_forwards(self, app, schema_editor, from_state, to_state): + app_cache = to_state.render() + schema_editor.create_model(app_cache.get_model(app, self.name)) + + def database_backwards(self, app, schema_editor, from_state, to_state): + """ + Performs the mutation on the database schema in the reverse + direction - e.g. if this were CreateModel, it would in fact + drop the model's table. + """ + raise NotImplementedError() diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py new file mode 100644 index 0000000000..3dbbbe27f8 --- /dev/null +++ b/django/db/migrations/state.py @@ -0,0 +1,81 @@ +from django.db import models +from django.db.models.loading import BaseAppCache + + +class ProjectState(object): + """ + Represents the entire project's overall state. + This is the item that is passed around - we do it here rather than at the + app level so that cross-app FKs/etc. resolve properly. + """ + + def __init__(self, models=None): + self.models = models or {} + self.app_cache = None + + def clone(self): + "Returns an exact copy of this ProjectState" + ps = 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" + if self.app_cache is None: + self.app_cache = BaseAppCache() + for model in self.model.values: + model.render(self.app_cache) + return self.app_cache + + +class ModelState(object): + """ + Represents a Django Model. We don't use the actual Model class + as it's not designed to have its options changed - instead, we + 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 + self.app_label = app_label + self.name = name + self.fields = fields or [] + self.options = options or {} + self.bases = bases or 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, + options = self.options, + bases = self.bases, + ) + + def render(self, app_cache): + "Creates a Model object from our current state into the given app_cache" + # First, make a Meta object + meta_contents = {'app_label': self.app_label, "app_cache": app_cache} + meta_contents.update(self.options) + meta = type("Meta", tuple(), meta_contents) + # Then, work out our bases + # TODO: Use the actual bases + if self.bases: + raise NotImplementedError("Custom bases not quite done yet!") + else: + bases = [models.Model] + # Turn fields into a dict for the body, add other bits + body = dict(self.fields) + body['Meta'] = meta + body['__module__'] = "__fake__" + # Then, make a Model object + return type( + self.name, + tuple(bases), + body, + )