Commit 8f4877c8 authored by Loic Bistuer's avatar Loic Bistuer
Browse files

Fixed #22583 -- Allowed RunPython and RunSQL to provide hints to the db router.

Thanks Markus Holtermann and Tim Graham for the review.
parent 665e0aa6
Loading
Loading
Loading
Loading
+7 −9
Original line number Diff line number Diff line
@@ -98,17 +98,15 @@ class Operation(object):
        """
        return self.references_model(model_name, app_label)

    def allowed_to_migrate(self, connection_alias, model):
    def allowed_to_migrate(self, connection_alias, model, hints=None):
        """
        Returns if we're allowed to migrate the model. Checks the router,
        if it's a proxy, if it's managed, and if it's swapped out.
        Returns if we're allowed to migrate the model.
        """
        return (
            not model._meta.proxy and
            not model._meta.swapped and
            model._meta.managed and
            router.allow_migrate(connection_alias, model)
        )
        # Always skip if proxy, swapped out, or unmanaged.
        if model and (model._meta.proxy or model._meta.swapped or not model._meta.managed):
            return False

        return router.allow_migrate(connection_alias, model, **(hints or {}))

    def __repr__(self):
        return "<%s %s%s>" % (
+20 −10
Original line number Diff line number Diff line
@@ -63,10 +63,11 @@ class RunSQL(Operation):
    """
    noop = ''

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

    def deconstruct(self):
        kwargs = {
@@ -76,6 +77,8 @@ class RunSQL(Operation):
            kwargs['reverse_sql'] = self.reverse_sql
        if self.state_operations:
            kwargs['state_operations'] = self.state_operations
        if self.hints:
            kwargs['hints'] = self.hints
        return (
            self.__class__.__name__,
            [],
@@ -91,11 +94,13 @@ class RunSQL(Operation):
            state_operation.state_forwards(app_label, state)

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
            self._run_sql(schema_editor, self.sql)

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        if self.reverse_sql is None:
            raise NotImplementedError("You cannot reverse this operation")
        if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
            self._run_sql(schema_editor, self.reverse_sql)

    def describe(self):
@@ -125,7 +130,7 @@ class RunPython(Operation):

    reduces_to_sql = False

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

    def deconstruct(self):
        kwargs = {
@@ -147,6 +153,8 @@ class RunPython(Operation):
            kwargs['reverse_code'] = self.reverse_code
        if self.atomic is not True:
            kwargs['atomic'] = self.atomic
        if self.hints:
            kwargs['hints'] = self.hints
        return (
            self.__class__.__name__,
            [],
@@ -163,6 +171,7 @@ class RunPython(Operation):
        pass

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
            # We now execute the Python code in a context that contains a 'models'
            # object, representing the versioned models as an app registry.
            # We could try to override the global cache, but then people will still
@@ -172,6 +181,7 @@ class RunPython(Operation):
    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        if self.reverse_code is None:
            raise NotImplementedError("You cannot reverse this operation")
        if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
            self.reverse_code(from_state.apps, schema_editor)

    def describe(self):
+2 −2
Original line number Diff line number Diff line
@@ -316,7 +316,7 @@ class ConnectionRouter(object):
                    return allow
        return obj1._state.db == obj2._state.db

    def allow_migrate(self, db, model):
    def allow_migrate(self, db, model, **hints):
        for router in self.routers:
            try:
                try:
@@ -331,7 +331,7 @@ class ConnectionRouter(object):
                # If the router doesn't have a method, skip to the next one.
                pass
            else:
                allow = method(db, model)
                allow = method(db, model, **hints)
                if allow is not None:
                    return allow
        return True
+18 −2
Original line number Diff line number Diff line
@@ -206,7 +206,7 @@ Special Operations
RunSQL
------

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

Allows running of arbitrary SQL on the database - useful for more advanced
features of database backends that Django doesn't support directly, like
@@ -235,6 +235,11 @@ operation here so that the autodetector still has an up-to-date state of the
model (otherwise, when you next run ``makemigrations``, it won't see any
operation that adds that field and so will try to run it again).

The optional ``hints`` argument will be passed as ``**hints`` to the
:meth:`allow_migrate` method of database routers to assist them in making
routing decisions. See :ref:`topics-db-multi-db-hints` for more details on
database hints.

.. versionchanged:: 1.7.1

    If you want to include literal percent signs in a query without parameters
@@ -245,6 +250,8 @@ operation that adds that field and so will try to run it again).
    The ability to pass parameters to the ``sql`` and ``reverse_sql`` queries
    was added.

    The ``hints`` argument was added.

.. attribute:: RunSQL.noop

    .. versionadded:: 1.8
@@ -258,7 +265,7 @@ operation that adds that field and so will try to run it again).
RunPython
---------

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

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
@@ -267,6 +274,15 @@ match the operation's place in the project history, and the second is an
instance of :class:`SchemaEditor
<django.db.backends.schema.BaseDatabaseSchemaEditor>`.

The optional ``hints`` argument will be passed as ``**hints`` to the
:meth:`allow_migrate` method of database routers to assist them in making a
routing decision. See :ref:`topics-db-multi-db-hints` for more details on
database hints.

.. versionadded:: 1.8

    The ``hints`` argument was added.

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``
+14 −0
Original line number Diff line number Diff line
@@ -462,6 +462,12 @@ Migrations
  attribute/method were added to ease in making ``RunPython`` and ``RunSQL``
  operations reversible.

* The :class:`~django.db.migrations.operations.RunPython` and
  :class:`~django.db.migrations.operations.RunSQL` operations now accept a
  ``hints`` parameter that will be passed to :meth:`allow_migrate`. To take
  advantage of this feature you must ensure that the ``allow_migrate()`` method
  of all your routers accept ``**hints``.

Models
^^^^^^

@@ -1029,6 +1035,14 @@ Miscellaneous
* :func:`django.utils.translation.get_language()` now returns ``None`` instead
  of :setting:`LANGUAGE_CODE` when translations are temporarily deactivated.

* The migration operations :class:`~django.db.migrations.operations.RunPython`
  and :class:`~django.db.migrations.operations.RunSQL` now call the
  :meth:`allow_migrate` method of database routers. In these cases the
  ``model`` argument of ``allow_migrate()`` is set to ``None``, so the router
  must properly handle this value. This is most useful when used together with
  the newly introduced ``hints`` parameter for these operations, but it can
  also be used to disable migrations from running on a particular database.

.. _deprecated-features-1.8:

Features deprecated in 1.8
Loading