#24346 closed Bug (fixed)
AuthRouter docs lead to AttributeError: 'NoneType' object has no attribute '_meta' with 0002_remove_content_type_name
| Reported by: | Peter Schmidt | Owned by: | nobody |
|---|---|---|---|
| Component: | Documentation | Version: | 1.8alpha1 |
| Severity: | Release blocker | Keywords: | |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description
#22583 passes model=None into allow_migrate(db, model, **hints):
https://github.com/django/django/blob/8f4877c89d0f28e289399fbb46357623a49e7eb0/django/db/migrations/operations/special.py#L174
#24099 introduced a 0002_remove_content_type_name migration which uses this code path:
https://github.com/django/django/blob/b4ac23290772e0c11379eb2dfb81c750b7052b66/django/contrib/contenttypes/migrations/0002_remove_content_type_name.py
When combined with the documented AuthRouter example:
https://docs.djangoproject.com/en/1.8/topics/db/multi-db/#an-example
This causes a stacktrace like the following in downstream projects:
$ ./manage.py test --verbosity=2
Creating test database for alias 'default' (':memory:')...
...
Running migrations:
Rendering model states... DONE
...
Applying contenttypes.0002_remove_content_type_name...Traceback (most recent call last):
File "./manage.py", line 20, in <module>
execute_from_command_line(sys.argv)
File "/Users/pzrq/Projects/django/django/core/management/__init__.py", line 338, in execute_from_command_line
utility.execute()
File "/Users/pzrq/Projects/django/django/core/management/__init__.py", line 330, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/Users/pzrq/Projects/django/django/core/management/commands/test.py", line 30, in run_from_argv
super(Command, self).run_from_argv(argv)
File "/Users/pzrq/Projects/django/django/core/management/base.py", line 390, in run_from_argv
self.execute(*args, **cmd_options)
File "/Users/pzrq/Projects/django/django/core/management/commands/test.py", line 74, in execute
super(Command, self).execute(*args, **options)
File "/Users/pzrq/Projects/django/django/core/management/base.py", line 441, in execute
output = self.handle(*args, **options)
File "/Users/pzrq/Projects/django/django/core/management/commands/test.py", line 90, in handle
failures = test_runner.run_tests(test_labels)
File "/Users/pzrq/Projects/django/django/test/runner.py", line 210, in run_tests
old_config = self.setup_databases()
File "/Users/pzrq/Projects/django/django/test/runner.py", line 166, in setup_databases
**kwargs
File "/Users/pzrq/Projects/django/django/test/runner.py", line 370, in setup_databases
serialize=connection.settings_dict.get("TEST", {}).get("SERIALIZE", True),
File "/Users/pzrq/Projects/django/django/db/backends/base/creation.py", line 369, in create_test_db
test_flush=True,
File "/Users/pzrq/Projects/django/django/core/management/__init__.py", line 120, in call_command
return command.execute(*args, **defaults)
File "/Users/pzrq/Projects/django/django/core/management/base.py", line 441, in execute
output = self.handle(*args, **options)
File "/Users/pzrq/Projects/django/django/core/management/commands/migrate.py", line 213, in handle
executor.migrate(targets, plan, fake=options.get("fake", False))
File "/Users/pzrq/Projects/django/django/db/migrations/executor.py", line 93, in migrate
self.apply_migration(states[migration], migration, fake=fake)
File "/Users/pzrq/Projects/django/django/db/migrations/executor.py", line 129, in apply_migration
state = migration.apply(state, schema_editor)
File "/Users/pzrq/Projects/django/django/db/migrations/migration.py", line 110, in apply
operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
File "/Users/pzrq/Projects/django/django/db/migrations/operations/special.py", line 174, in database_forwards
if self.allowed_to_migrate(schema_editor.connection.alias, None, hints=self.hints):
File "/Users/pzrq/Projects/django/django/db/migrations/operations/base.py", line 109, in allowed_to_migrate
return router.allow_migrate(connection_alias, model, **(hints or {}))
File "/Users/pzrq/Projects/django/django/db/utils.py", line 334, in allow_migrate
allow = method(db, model, **hints)
File "/Users/pzrq/Projects/mathspace/msproblem/core/database_routers.py", line 67, in allow_migrate
elif model._meta.app_label in APP_LABELS:
AttributeError: 'NoneType' object has no attribute '_meta'
This is documented here:
https://docs.djangoproject.com/en/1.8/releases/1.8/#miscellaneous
The migration operations RunPython and RunSQL now call the 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.
So I guess the fix is to update the docs to be consistent with the previously committed changes, e.g.
class AuthRouter(object):
...
def allow_migrate(self, db, model=None, **hints):
"""
Make sure the auth app only appears in the 'auth_db'
database.
"""
if model is None:
# Handle RunPython or RunSQL
return db == 'auth_db'
if db == 'auth_db':
return model._meta.app_label == 'auth'
elif model._meta.app_label == 'auth':
return False
return None
Will add a Github PR for that shortly.
Change History (8)
comment:1 by , 11 years ago
comment:2 by , 11 years ago
| Has patch: | set |
|---|---|
| Patch needs improvement: | set |
Returning None (no opinion) is the most sensible default in the absence of hints when model == None.
That said we should probably add hints to that content type migration in 1.8.
comment:3 by , 11 years ago
| Severity: | Normal → Release blocker |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
comment:5 by , 11 years ago
| Resolution: | → duplicate |
|---|---|
| Status: | new → closed |
Closing as duplicate of #24351 -- thanks for raising it.
comment:6 by , 11 years ago
| Resolution: | duplicate → fixed |
|---|
comment:8 by , 11 years ago
Thanks Loïc, Aymeric, Tim and everyone else who contributed to this.
Works for me, in practice I have tended to split up apps with a large number of models in them into smaller apps which focus on one or a few closely related models, so app_label was enough, though it's really cool to see how much thought and effort goes into this area.
https://github.com/django/django/pull/4143