Commit 729e0b08 authored by Simon Charette's avatar Simon Charette
Browse files

Fixed #24109 -- Allowed RunSQL and RunPython operations to be elided.

Thanks to Markus Holtermann and Tim Graham for their review.
parent 49f4c9f4
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -30,6 +30,9 @@ class Operation(object):
    # DDL transaction support (i.e., does it have no DDL, like RunPython)
    atomic = False

    # Should this operation be considered safe to elide and optimize across?
    elidable = False

    serialization_expand_args = []

    def __new__(cls, *args, **kwargs):
@@ -117,6 +120,10 @@ class Operation(object):
        replaced with or a boolean that indicates whether or not the specified
        operation can be optimized across.
        """
        if self.elidable:
            return [operation]
        elif operation.elidable:
            return [self]
        return False

    def __repr__(self):
+10 −2
Original line number Diff line number Diff line
@@ -27,7 +27,10 @@ class FieldOperation(Operation):
        return self.is_same_model_operation(operation) and self.name_lower == operation.name_lower

    def reduce(self, operation, in_between, app_label=None):
        return not operation.references_field(self.model_name, self.name, app_label)
        return (
            super(FieldOperation, self).reduce(operation, in_between, app_label=app_label) or
            not operation.references_field(self.model_name, self.name, app_label)
        )


class AddField(FieldOperation):
@@ -333,4 +336,9 @@ class RenameField(FieldOperation):
                    operation.new_name,
                ),
            ]
        return not operation.references_field(self.model_name, self.new_name, app_label)
        # Skip `FieldOperation.reduce` as we want to run `references_field`
        # against self.new_name.
        return (
            super(FieldOperation, self).reduce(operation, in_between, app_label=app_label) or
            not operation.references_field(self.model_name, self.new_name, app_label)
        )
+10 −2
Original line number Diff line number Diff line
@@ -21,7 +21,10 @@ class ModelOperation(Operation):
        return self.name.lower()

    def reduce(self, operation, in_between, app_label=None):
        return not operation.references_model(self.name, app_label)
        return (
            super(ModelOperation, self).reduce(operation, in_between, app_label=app_label) or
            not operation.references_model(self.name, app_label)
        )


class CreateModel(ModelOperation):
@@ -365,7 +368,12 @@ class RenameModel(ModelOperation):
                    operation.new_name,
                ),
            ]
        return not operation.references_model(self.new_name, app_label)
        # Skip `ModelOperation.reduce` as we want to run `references_model`
        # against self.new_name.
        return (
            super(ModelOperation, self).reduce(operation, in_between, app_label=app_label) or
            not operation.references_model(self.new_name, app_label)
        )


class AlterModelTable(ModelOperation):
+4 −2
Original line number Diff line number Diff line
@@ -71,11 +71,12 @@ class RunSQL(Operation):
    """
    noop = ''

    def __init__(self, sql, reverse_sql=None, state_operations=None, hints=None):
    def __init__(self, sql, reverse_sql=None, state_operations=None, hints=None, elidable=False):
        self.sql = sql
        self.reverse_sql = reverse_sql
        self.state_operations = state_operations or []
        self.hints = hints or {}
        self.elidable = elidable

    def deconstruct(self):
        kwargs = {
@@ -138,7 +139,7 @@ class RunPython(Operation):

    reduces_to_sql = False

    def __init__(self, code, reverse_code=None, atomic=True, hints=None):
    def __init__(self, code, reverse_code=None, atomic=True, hints=None, elidable=False):
        self.atomic = atomic
        # Forwards code
        if not callable(code):
@@ -152,6 +153,7 @@ class RunPython(Operation):
                raise ValueError("RunPython must be supplied with callable arguments")
            self.reverse_code = reverse_code
        self.hints = hints or {}
        self.elidable = elidable

    def deconstruct(self):
        kwargs = {
+16 −2
Original line number Diff line number Diff line
@@ -196,7 +196,7 @@ Special Operations
RunSQL
------

.. class:: RunSQL(sql, reverse_sql=None, state_operations=None, hints=None)
.. class:: RunSQL(sql, reverse_sql=None, state_operations=None, hints=None, elidable=False)

Allows running of arbitrary SQL on the database - useful for more advanced
features of database backends that Django doesn't support directly, like
@@ -249,6 +249,9 @@ The optional ``hints`` argument will be passed as ``**hints`` to the
routing decisions. See :ref:`topics-db-multi-db-hints` for more details on
database hints.

The optional ``elidable`` argument determines whether or not the operation will
be removed (elided) when :ref:`squashing migrations <migration-squashing>`.

.. attribute:: RunSQL.noop

    Pass the ``RunSQL.noop`` attribute to ``sql`` or ``reverse_sql`` when you
@@ -257,10 +260,14 @@ database hints.

.. _sqlparse: https://pypi.python.org/pypi/sqlparse

.. versionadded:: 1.10

    The ``elidable`` argument was added.

RunPython
---------

.. class:: RunPython(code, reverse_code=None, atomic=True, hints=None)
.. class:: RunPython(code, reverse_code=None, atomic=True, hints=None, elidable=False)

Runs custom Python code in a historical context. ``code`` (and ``reverse_code``
if supplied) should be callable objects that accept two arguments; the first is
@@ -278,6 +285,9 @@ The optional ``hints`` argument will be passed as ``**hints`` to the
routing decision. See :ref:`topics-db-multi-db-hints` for more details on
database hints.

The optional ``elidable`` argument determines whether or not the operation will
be removed (elided) when :ref:`squashing migrations <migration-squashing>`.

You are advised to write the code as a separate function above the ``Migration``
class in the migration file, and just pass it to ``RunPython``. Here's an
example of using ``RunPython`` to create some initial objects on a ``Country``
@@ -366,6 +376,10 @@ attribute.
    you want the operation not to do anything in the given direction. This is
    especially useful in making the operation reversible.

.. versionadded:: 1.10

    The ``elidable`` argument was added.

SeparateDatabaseAndState
------------------------

Loading