Hi all,
The proposed fix for the release blocker
https://code.djangoproject.com/ticket/24351 suggests changing the signature of the `allow_migrate` database router.
From:
def allow_migrate(self, db, model):
To:
def allow_migrate(self, db, app_label, model, **hints):
We need to make a design decision.
Some background:
1. Django 1.7 doesn't call the `allow_migrate` router for RunPython and RunSQL operations. If you control all migrations you can somewhat work around the issue inside RunPython using
https://github.com/django/django/blob/1.7.4/docs/topics/migrations.txt#L472-L500, but you still have to stay away from RunSQL, and you are out of luck if these operations live in django.contrib or in a third-party app.
2. Behavior from 1. changed in Django 1.8 (#22583) and the RunPython and RunSQL operations now call `allow_migrate` with `model=None`. These operations also accept a `hints` kwarg that is passed as `**hints` to `allow_migrate`. Unless hints are provided `allow_migrate` can only make a blanket yes/no decision because it doesn't have anything other than the `db_alias` to work with.
3. The change from 2. is backwards incompatible in a sense that routers will blow up if they don't expect None as a possible value for `model`. The `hints` part is backwards compatible as long as no migrations make use of them.
4. Django 1.8 introduced a migration for contrib.contenttypes that contains a RunPython operation (
https://github.com/django/django/blob/b4ac23290772e0c11379eb2dfb81c750b7052b66/django/contrib/contenttypes/migrations/0002_remove_content_type_name.py#L33-L36). This is a problem because it won't work in many multi-db setups. The problem already existed for third-party apps but now it becomes a core issue.
5. We could take advantage of 2. to fix 4. (e.g. `RunPython(noop, add_legacy_name, hints={'app_label': 'contenttypes'})`) which would allow multi-db users to make a routing decision, but then we introduce a backwards incompatibility (see point 3.) that requires changing the signature of `allow_migrate` in user code (to expect either the `app_label` kwarg or `**hints`). While this would fix the problem for contrib.contenttypes, the problem would still exist in third-party apps unless they also cooperate by providing similar hints (note that those that still want to support 1.7 projects won't be able to do so).
6. `app_label` applies to all operations including RunPython and RunSQL, `model` only applies to some model operations (CreateModel, AddField, etc.). Our docs and tests only use the `model` argument to dig out the `app_label`, and I suspect this is the most common usage (at least it certainly matches my usage).
My opinion is that `app_label` is always useful in `allow_migrate`, even if only as a first pass before inspecting the model, so since we are introducing a change in the signature of `allow_migrate`, we might as well bite the bullet and make it a required argument. The latest version of my branch
https://github.com/django/django/pull/4152 does this while still supporting the old signature during a deprecation period.
An alternative that was proposed by Markus is to pass `app_label` as a hint instead of arg from within `allowed_to_migrate`. I don't think it's a good idea because it's just as invasive (the signature of existing routers still need to change) and we make it less obvious to people calling `router.allow_migrate()` directly that they should supply an `app_label`. It's IMO more error prone if we can't reliably expect that `app_label` will be provided because it requires writing additional defensive code.
Thoughts?
--
Loïc