Opened 19 months ago

Last modified 12 months ago

#26401 new New feature

Allow auth machinery to be used without installing auth app

Reported by: Matt Johnson Owned by: Andrew Konoff
Component: contrib.auth Version: 1.9
Severity: Normal Keywords: auth
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Django projects should be able to use the auth machinery (like django.contrib.auth.middleware.AuthenticationMiddleware and django.contrib.auth.context_processors.auth) without having django.contrib.auth in INSTALLED_APPS

See

https://groups.google.com/forum/#!topic/django-developers/2DzLBbk8w-w

This ticket is for resolving the current issue in 1.9. I also want to write a test or two to prevent this issue from happening in the future.

Attachments (1)

ticket-26401-remove-stale-contenttypes.patch (2.9 KB) - added by Jon Dufresne 13 months ago.
Test case for remove_stale_contenttypes with auth migrations disabled

Download all attachments as: .zip

Change History (27)

comment:1 Changed 19 months ago by Matt Johnson

Owner: changed from nobody to Matt Johnson
Status: newassigned

comment:2 Changed 19 months ago by Florian Apolloner

Triage Stage: UnreviewedAccepted

comment:4 Changed 19 months ago by Tim Graham

Needs tests: set

comment:5 Changed 17 months ago by Carl Meyer

Owner: Matt Johnson deleted
Status: assignednew

De-assigning so it's clear this is available for a PyCon sprinter to finish up with tests.

comment:6 Changed 17 months ago by Andrew Konoff

Owner: set to Andrew Konoff
Status: newassigned

comment:7 Changed 16 months ago by Matt Johnson

comment:8 Changed 14 months ago by Aymeric Augustin

FWIW I don't see a lot of value in adding this constraint. Implementing it requires a fragile and untestable combination of inner imports to avoid touching the models module.

If the only problem is to avoid creating tables in the database, add a MIGRATION_MODULES entry for the auth app that points to an empty package. I never tried it but I think it should work.

If this workaround is confirmed to work, I propose to close this ticket as wontfix.

comment:9 Changed 14 months ago by Matt Johnson

Interesting idea. It works:

MIGRATION_MODULES = {
    'auth': None
}

My complaint was driven by the unnecessary clutter in my database when using a custom user model. So now that there is a reasonable solution, I'm fine with closing this.

comment:10 Changed 14 months ago by Matt Johnson

Resolution: wontfix
Status: assignedclosed

comment:11 Changed 14 months ago by Aymeric Augustin

Great! Thanks for reporting back.

comment:12 Changed 14 months ago by Jon Dufresne

I found the above to be insufficient. Adding MIGRATION_MODULES as the only change to avoid the auth tables fails during migrations with the following trace:

Traceback (most recent call last):
  File ".../manage.py", line 9, in <module>
    execute_from_command_line(sys.argv)
  File ".../venv/lib64/python3.5/site-packages/django/core/management/__init__.py", line 367, in execute_from_command_line
    utility.execute()
  File ".../venv/lib64/python3.5/site-packages/django/core/management/__init__.py", line 359, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File ".../venv/lib64/python3.5/site-packages/django/core/management/base.py", line 305, in run_from_argv
    self.execute(*args, **cmd_options)
  File ".../venv/lib64/python3.5/site-packages/django/core/management/base.py", line 356, in execute
    output = self.handle(*args, **options)
  File ".../venv/lib64/python3.5/site-packages/django/core/management/commands/migrate.py", line 222, in handle
    self.verbosity, self.interactive, connection.alias, apps=post_migrate_apps, plan=plan,
  File ".../venv/lib64/python3.5/site-packages/django/core/management/sql.py", line 53, in emit_post_migrate_signal
    **kwargs
  File ".../venv/lib64/python3.5/site-packages/django/dispatch/dispatcher.py", line 191, in send
    response = receiver(signal=self, sender=sender, **named)
  File ".../venv/lib64/python3.5/site-packages/django/contrib/auth/management/__init__.py", line 75, in create_permissions
    "content_type", "codename"
  File ".../venv/lib64/python3.5/site-packages/django/db/models/query.py", line 256, in __iter__
    self._fetch_all()
  File ".../venv/lib64/python3.5/site-packages/django/db/models/query.py", line 1085, in _fetch_all
    self._result_cache = list(self.iterator())
  File ".../venv/lib64/python3.5/site-packages/django/db/models/query.py", line 125, in __iter__
    for row in compiler.results_iter():
  File ".../venv/lib64/python3.5/site-packages/django/db/models/sql/compiler.py", line 789, in results_iter
    results = self.execute_sql(MULTI)
  File ".../venv/lib64/python3.5/site-packages/django/db/models/sql/compiler.py", line 835, in execute_sql
    cursor.execute(sql, params)
  File ".../venv/lib64/python3.5/site-packages/django/db/backends/utils.py", line 79, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File ".../venv/lib64/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File ".../venv/lib64/python3.5/site-packages/django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File ".../venv/lib64/python3.5/site-packages/django/utils/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File ".../venv/lib64/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "auth_permission" does not exist
LINE 1: ...ntent_type_id", "auth_permission"."codename" FROM "auth_perm...

This fails becuase the auth's AppConfig adds a post_migrate callback that requires the auth_permission table to exist. A project could avoid this by overriding the auth's AppConfig. I think this could be easier for projects if the auth's AppConfig was split into two classes, one base version that can be used without the auth tables and one that matches the current implementation. This would be a backwards compatible change that would allow the MIGRATION_MODULES change noted above. For example:

class BaseAuthConfig(AppConfig):
    name = 'django.contrib.auth'
    verbose_name = _("Authentication and Authorization")

    def ready(self):
        checks.register(check_user_model, checks.Tags.models)

class AuthConfig(BaseAuthConfig):
    def ready(self):
        post_migrate.connect(
            create_permissions,
            dispatch_uid="django.contrib.auth.management.create_permissions"
        )
        checks.register(check_models_permissions, checks.Tags.models)

comment:13 Changed 14 months ago by Claude Paroz

Resolution: wontfix
Status: closednew

comment:14 Changed 14 months ago by Jon Dufresne

PR implementing the AppConfigs described above.

Not sure how to best add a test for this though. Looking for advice. Details in the PR.

comment:15 Changed 14 months ago by Jon Dufresne

Needs tests: unset

A test has been added to the PR above.

comment:16 Changed 13 months ago by Tim Graham

Easy pickings: unset
Patch needs improvement: set

comment:17 Changed 13 months ago by Tim Graham

Patch needs improvement: unset
Triage Stage: AcceptedReady for checkin

comment:18 Changed 13 months ago by GitHub <noreply@…>

Resolution: fixed
Status: newclosed

In 1ec1633c:

Fixed #26401 -- Added BaseAuthConfig to use auth without migrations.

comment:19 Changed 13 months ago by Jon Dufresne

I've hit another snag with the MIGRATION_MODULES = {'auth': None} approach. (Sorry, is it better to reopen this ticket or file a new one?)

The auth.Permission model has a fk on ContentType. Due to this, when deleting a ContentType (such as with the remove_stale_contenttypes command) queries against the missing auth_permission table can occur.

Not sure how best to handle this scenario. IIUC, as long as auth is in INSTALLED_APPS, the models will be loaded and this FK will cause an issue.

Here is a stack trace when deleting a stale content type

Traceback (most recent call last):
  File "/usr/lib64/python3.5/runpy.py", line 170, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib64/python3.5/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/jon/devel/django/django/__main__.py", line 9, in <module>
    management.execute_from_command_line()
  File "/home/jon/devel/django/django/core/management/__init__.py", line 366, in execute_from_command_line
    utility.execute()
  File "/home/jon/devel/django/django/core/management/__init__.py", line 358, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/jon/devel/django/django/core/management/base.py", line 294, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/jon/devel/django/django/core/management/base.py", line 345, in execute
    output = self.handle(*args, **options)
  File "/home/jon/devel/django/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py", line 46, in handle
    collector.collect([ct])
  File "/home/jon/devel/django/django/db/models/deletion.py", line 218, in collect
    elif sub_objs:
  File "/home/jon/devel/django/django/db/models/query.py", line 260, in __bool__
    self._fetch_all()
  File "/home/jon/devel/django/django/db/models/query.py", line 1072, in _fetch_all
    self._result_cache = list(self.iterator())
  File "/home/jon/devel/django/django/db/models/query.py", line 54, in __iter__
    results = compiler.execute_sql()
  File "/home/jon/devel/django/django/db/models/sql/compiler.py", line 847, in execute_sql
    cursor.execute(sql, params)
  File "/home/jon/devel/django/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/jon/devel/django/django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/jon/devel/django/django/utils/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/home/jon/devel/django/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/jon/devel/django/django/db/backends/sqlite3/base.py", line 334, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table: auth_permission

Changed 13 months ago by Jon Dufresne

Test case for remove_stale_contenttypes with auth migrations disabled

comment:20 Changed 13 months ago by Tim Graham

Has patch: unset
Keywords: 1.11 added
Triage Stage: Ready for checkinAccepted
Type: BugNew feature

It's appropriate to reopen a ticket if the fix hasn't been released yet.

comment:21 Changed 13 months ago by Jon Dufresne

Resolution: fixed
Status: closednew

Reopening until the above issue is handled.

comment:22 Changed 12 months ago by Jon Dufresne

Has patch: set

I'm unable to think of a sensible solution to the ContentType auth.Permission dependency issue. Therefore, I'm suggesting to revert my previous PR as using auth without migrations will not fully work.

PR to revert

comment:23 Changed 12 months ago by Tim Graham

It seems like you basically want an apps.register.Apps.unregister_model() method or some way of telling Django not to setup relations for a given model. This might not be trivial, so if you want to revert for now, that's fine with me.

comment:24 Changed 12 months ago by Jon Dufresne <jon.dufresne@…>

Resolution: fixed
Status: newclosed

In f3ea0c4:

Reverted "Fixed #26401 -- Added BaseAuthConfig to use auth without migrations."

This reverts commit 1ec1633cb294d8ce2a65ece6b56c258483596fba as it
doesn't handle ContentType's auth.Permission dependency. Thus, it
doesn't allow auth without migrations.

comment:25 Changed 12 months ago by Jon Dufresne

Resolution: fixed
Status: closednew

Reopening. The revert commit was merged.

comment:26 Changed 12 months ago by Tim Graham

Has patch: unset
Keywords: 1.11 removed
Note: See TracTickets for help on using tickets.
Back to Top