Commit a8ce5fdc authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Fixed #22470: Full migration support for order_with_respect_to

parent a58f49d1
Loading
Loading
Loading
Loading
+56 −2
Original line number Diff line number Diff line
@@ -163,6 +163,7 @@ class MigrationAutodetector(object):
        self.generate_altered_fields()
        self.generate_altered_unique_together()
        self.generate_altered_index_together()
        self.generate_altered_order_with_respect_to()

        # Now, reordering to make things possible. The order we have already
        # isn't bad, but we need to pull a few things around so FKs work nicely
@@ -305,9 +306,16 @@ class MigrationAutodetector(object):
                operation.model_name.lower() == dependency[1].lower() and
                operation.name.lower() == dependency[2].lower()
            )
        # order_with_respect_to being unset for a field
        elif dependency[2] is not None and dependency[3] == "order_wrt_unset":
            return (
                isinstance(operation, operations.AlterOrderWithRespectTo) and
                operation.name.lower() == dependency[1].lower() and
                (operation.order_with_respect_to or "").lower() != dependency[2].lower()
            )
        # Unknown dependency. Raise an error.
        else:
            raise ValueError("Can't handle dependency %r" % dependency)
            raise ValueError("Can't handle dependency %r" % (dependency, ))

    def add_operation(self, app_label, operation, dependencies=None):
        # Dependencies are (app_label, model_name, field_name, create/delete as True/False)
@@ -375,6 +383,7 @@ class MigrationAutodetector(object):
            # Are there unique/index_together to defer?
            unique_together = model_state.options.pop('unique_together', None)
            index_together = model_state.options.pop('index_together', None)
            order_with_respect_to = model_state.options.pop('order_with_respect_to', None)
            # Generate creation operatoin
            self.add_operation(
                app_label,
@@ -438,6 +447,17 @@ class MigrationAutodetector(object):
                        for name, field in sorted(related_fields.items())
                    ]
                )
            if order_with_respect_to:
                self.add_operation(
                    app_label,
                    operations.AlterOrderWithRespectTo(
                        name=model_name,
                        order_with_respect_to=order_with_respect_to,
                    ),
                    dependencies=[
                        (app_label, model_name, order_with_respect_to, True),
                    ]
                )

    def generate_deleted_models(self):
        """
@@ -595,7 +615,10 @@ class MigrationAutodetector(object):
                operations.RemoveField(
                    model_name=model_name,
                    name=field_name,
                )
                ),
                # We might need to depend on the removal of an order_with_respect_to;
                # this is safely ignored if there isn't one
                dependencies=[(app_label, model_name, field_name, "order_wrt_unset")],
            )

    def generate_altered_fields(self):
@@ -659,6 +682,11 @@ class MigrationAutodetector(object):
                )

    def generate_altered_options(self):
        """
        Works out if any non-schema-affecting options have changed and
        makes an operation to represent them in state changes (in case Python
        code in migrations needs them)
        """
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get((app_label, model_name), model_name)
            old_model_state = self.from_state.models[app_label, old_model_name]
@@ -680,6 +708,32 @@ class MigrationAutodetector(object):
                    )
                )

    def generate_altered_order_with_respect_to(self):
        for app_label, model_name in sorted(self.kept_model_keys):
            old_model_name = self.renamed_models.get((app_label, model_name), model_name)
            old_model_state = self.from_state.models[app_label, old_model_name]
            new_model_state = self.to_state.models[app_label, model_name]
            if old_model_state.options.get("order_with_respect_to", None) != new_model_state.options.get("order_with_respect_to", None):
                # Make sure it comes second if we're adding
                # (removal dependency is part of RemoveField)
                dependencies = []
                if new_model_state.options.get("order_with_respect_to", None):
                    dependencies.append((
                        app_label,
                        model_name,
                        new_model_state.options["order_with_respect_to"],
                        True,
                    ))
                # Actually generate the operation
                self.add_operation(
                    app_label,
                    operations.AlterOrderWithRespectTo(
                        name=model_name,
                        order_with_respect_to=new_model_state.options.get('order_with_respect_to', None),
                    ),
                    dependencies = dependencies,
                )

    def arrange_for_graph(self, changes, graph):
        """
        Takes in a result from changes() and a MigrationGraph,
+3 −1
Original line number Diff line number Diff line
from .models import (CreateModel, DeleteModel, AlterModelTable,
    AlterUniqueTogether, AlterIndexTogether, RenameModel, AlterModelOptions)
    AlterUniqueTogether, AlterIndexTogether, RenameModel, AlterModelOptions,
    AlterOrderWithRespectTo)
from .fields import AddField, RemoveField, AlterField, RenameField
from .special import SeparateDatabaseAndState, RunSQL, RunPython

@@ -8,4 +9,5 @@ __all__ = [
    'RenameModel', 'AlterIndexTogether', 'AlterModelOptions',
    'AddField', 'RemoveField', 'AlterField', 'RenameField',
    'SeparateDatabaseAndState', 'RunSQL', 'RunPython',
    'AlterOrderWithRespectTo',
]
+39 −0
Original line number Diff line number Diff line
@@ -285,6 +285,45 @@ class AlterIndexTogether(Operation):
        return "Alter index_together for %s (%s constraints)" % (self.name, len(self.index_together))


class AlterOrderWithRespectTo(Operation):
    """
    Represents a change with the order_with_respect_to option.
    """

    def __init__(self, name, order_with_respect_to):
        self.name = name
        self.order_with_respect_to = order_with_respect_to

    def state_forwards(self, app_label, state):
        model_state = state.models[app_label, self.name.lower()]
        model_state.options['order_with_respect_to'] = self.order_with_respect_to

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        from_model = from_state.render().get_model(app_label, self.name)
        to_model = to_state.render().get_model(app_label, self.name)
        if router.allow_migrate(schema_editor.connection.alias, to_model):
            # Remove a field if we need to
            if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_to:
                schema_editor.remove_field(from_model, from_model._meta.get_field_by_name("_order")[0])
            # Add a field if we need to (altering the column is untouched as
            # it's likely a rename)
            elif to_model._meta.order_with_respect_to and not from_model._meta.order_with_respect_to:
                field = to_model._meta.get_field_by_name("_order")[0]
                schema_editor.add_field(
                    from_model,
                    field,
                )

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        self.database_forwards(app_label, schema_editor, from_state, to_state)

    def references_model(self, name, app_label=None):
        return name.lower() == self.name.lower()

    def describe(self):
        return "Set order_with_respect_to on %s to %s" % (self.name, self.order_with_respect_to)


class AlterModelOptions(Operation):
    """
    Sets new model options that don't directly affect the database schema
+3 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ from django.apps.registry import Apps, apps as global_apps
from django.db import models
from django.db.models.options import DEFAULT_NAMES, normalize_together
from django.db.models.fields.related import do_pending_lookups
from django.db.models.fields.proxy import OrderWrt
from django.conf import settings
from django.utils import six
from django.utils.encoding import force_text
@@ -166,6 +167,8 @@ class ModelState(object):
        for field in model._meta.local_fields:
            if getattr(field, "rel", None) and exclude_rels:
                continue
            if isinstance(field, OrderWrt):
                continue
            name, path, args, kwargs = field.deconstruct()
            field_class = import_string(path)
            try:
+18 −0
Original line number Diff line number Diff line
@@ -99,6 +99,24 @@ Changes the model's set of custom indexes (the
:attr:`~django.db.models.Options.index_together` option on the ``Meta``
subclass).

AlterOrderWithRespectTo
-----------------------

.. class:: AlterIndexTogether(name, order_with_respect_to)

Makes or deletes the ``_order`` column needed for the
:attr:`~django.db.models.Options.order_with_respect_to` option on the ``Meta``
subclass.

AlterModelOptions
-----------------

.. class:: AlterIndexTogether(name, options)

Stores changes to miscellaneous model options (settings on a model's ``Meta``)
like ``permissions`` and ``verbose_name``. Does not affect the database, but
persists these changes for :class:`RunPython` instances to use.

AddField
--------

Loading