Explorar o código

Fixed #21829 -- Added default AppConfigs.

Thanks Russell for the report, Marc for the initial patch, Carl for the
final review, and everyone who contributed to the design discussion.
Aymeric Augustin %!s(int64=11) %!d(string=hai) anos
pai
achega
2ff93e027c
Modificáronse 36 ficheiros con 190 adicións e 107 borrados
  1. 42 31
      django/apps/base.py
  2. 1 1
      django/conf/project_template/project_name/settings.py
  3. 2 3
      django/contrib/admin/__init__.py
  4. 13 2
      django/contrib/admin/apps.py
  5. 1 1
      django/contrib/admin/sites.py
  6. 1 0
      django/contrib/admindocs/__init__.py
  7. 3 6
      django/contrib/auth/__init__.py
  8. 5 0
      django/contrib/auth/apps.py
  9. 5 2
      django/contrib/comments/__init__.py
  10. 1 8
      django/contrib/contenttypes/__init__.py
  11. 5 1
      django/contrib/contenttypes/apps.py
  12. 1 0
      django/contrib/flatpages/__init__.py
  13. 1 0
      django/contrib/formtools/__init__.py
  14. 3 0
      django/contrib/gis/__init__.py
  15. 1 0
      django/contrib/humanize/__init__.py
  16. 3 0
      django/contrib/messages/__init__.py
  17. 1 0
      django/contrib/redirects/__init__.py
  18. 1 0
      django/contrib/sessions/__init__.py
  19. 3 0
      django/contrib/sitemaps/__init__.py
  20. 1 0
      django/contrib/sites/__init__.py
  21. 1 0
      django/contrib/staticfiles/__init__.py
  22. 1 0
      django/contrib/syndication/__init__.py
  23. 1 0
      django/contrib/webdesign/__init__.py
  24. 2 1
      django/core/checks/registry.py
  25. 1 8
      docs/intro/tutorial01.txt
  26. 28 7
      docs/ref/applications.txt
  27. 32 19
      docs/ref/contrib/admin/index.txt
  28. 1 1
      docs/ref/contrib/gis/tutorial.txt
  29. 10 7
      docs/releases/1.7.txt
  30. 2 3
      docs/topics/auth/default.txt
  31. 1 1
      tests/admin_scripts/tests.py
  32. 1 0
      tests/apps/default_config_app/__init__.py
  33. 5 0
      tests/apps/default_config_app/apps.py
  34. 6 0
      tests/apps/tests.py
  35. 1 1
      tests/i18n/tests.py
  36. 3 4
      tests/runtests.py

+ 42 - 31
django/apps/base.py

@@ -61,13 +61,12 @@ class AppConfig(object):
         Factory that creates an app config from an entry in INSTALLED_APPS.
         """
         try:
-            # If import_module succeeds, entry is a path to an app module.
+            # If import_module succeeds, entry is a path to an app module,
+            # which may specify an app config class with default_app_config.
             # Otherwise, entry is a path to an app config class or an error.
             module = import_module(entry)
 
         except ImportError:
-            # Avoid django.utils.module_loading.import_by_path because it
-            # masks errors -- it reraises ImportError as ImproperlyConfigured.
             mod_path, _, cls_name = entry.rpartition('.')
 
             # Raise the original exception when entry cannot be a path to an
@@ -75,39 +74,51 @@ class AppConfig(object):
             if not mod_path:
                 raise
 
-            mod = import_module(mod_path)
-            try:
-                cls = getattr(mod, cls_name)
-            except AttributeError:
-                # Emulate the error that "from <mod_path> import <cls_name>"
-                # would raise when <mod_path> exists but not <cls_name>, with
-                # more context (Python just says "cannot import name ...").
-                raise ImportError(
-                    "cannot import name '%s' from '%s'" % (cls_name, mod_path))
-
-            # Check for obvious errors. (This check prevents duck typing, but
-            # it could be removed if it became a problem in practice.)
-            if not issubclass(cls, AppConfig):
-                raise ImproperlyConfigured(
-                    "'%s' isn't a subclass of AppConfig." % entry)
-
-            # Obtain app name here rather than in AppClass.__init__ to keep
-            # all error checking for entries in INSTALLED_APPS in one place.
+        else:
             try:
-                app_name = cls.name
+                # If this works, the app module specifies an app config class.
+                entry = module.default_app_config
             except AttributeError:
-                raise ImproperlyConfigured(
-                    "'%s' must supply a name attribute." % entry)
+                # Otherwise, it simply uses the default app config class.
+                return cls(entry, module)
+            else:
+                mod_path, _, cls_name = entry.rpartition('.')
+
+        # If we're reaching this point, we must load the app config class
+        # located at <mod_path>.<cls_name>.
 
-            # Ensure app_name points to a valid module.
-            app_module = import_module(app_name)
+        # Avoid django.utils.module_loading.import_by_path because it
+        # masks errors -- it reraises ImportError as ImproperlyConfigured.
+        mod = import_module(mod_path)
+        try:
+            cls = getattr(mod, cls_name)
+        except AttributeError:
+            # Emulate the error that "from <mod_path> import <cls_name>"
+            # would raise when <mod_path> exists but not <cls_name>, with
+            # more context (Python just says "cannot import name ...").
+            raise ImportError(
+                "cannot import name '%s' from '%s'" % (cls_name, mod_path))
+
+        # Check for obvious errors. (This check prevents duck typing, but
+        # it could be removed if it became a problem in practice.)
+        if not issubclass(cls, AppConfig):
+            raise ImproperlyConfigured(
+                "'%s' isn't a subclass of AppConfig." % entry)
+
+        # Obtain app name here rather than in AppClass.__init__ to keep
+        # all error checking for entries in INSTALLED_APPS in one place.
+        try:
+            app_name = cls.name
+        except AttributeError:
+            raise ImproperlyConfigured(
+                "'%s' must supply a name attribute." % entry)
 
-            # Entry is a path to an app config class.
-            return cls(app_name, app_module)
+        # Ensure app_name points to a valid module.
+        app_module = import_module(app_name)
+
+        # Entry is a path to an app config class.
+        return cls(app_name, app_module)
 
-        else:
-            # Entry is a path to an app module.
-            return cls(entry, module)
 
     def get_model(self, model_name):
         """

+ 1 - 1
django/conf/project_template/project_name/settings.py

@@ -30,7 +30,7 @@ ALLOWED_HOSTS = []
 # Application definition
 
 INSTALLED_APPS = (
-    'django.contrib.admin.apps.AdminConfig',
+    'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',

+ 2 - 3
django/contrib/admin/__init__.py

@@ -1,6 +1,5 @@
 # ACTION_CHECKBOX_NAME is unused, but should stay since its import from here
 # has been referenced in documentation.
-from django.contrib.admin.checks import check_admin_app
 from django.contrib.admin.decorators import register
 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
@@ -9,7 +8,6 @@ from django.contrib.admin.filters import (ListFilter, SimpleListFilter,
     FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
     ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
 from django.contrib.admin.sites import AdminSite, site
-from django.core import checks
 from django.utils.module_loading import autodiscover_modules
 
 __all__ = [
@@ -24,4 +22,5 @@ __all__ = [
 def autodiscover():
     autodiscover_modules('admin', register_to=site)
 
-checks.register('admin')(check_admin_app)
+
+default_app_config = 'django.contrib.admin.apps.AdminConfig'

+ 13 - 2
django/contrib/admin/apps.py

@@ -1,11 +1,22 @@
 from django.apps import AppConfig
-
+from django.core import checks
+from django.contrib.admin.checks import check_admin_app
 from django.utils.translation import ugettext_lazy as _
 
 
-class AdminConfig(AppConfig):
+class SimpleAdminConfig(AppConfig):
+    """Simple AppConfig which does not do automatic discovery."""
+
     name = 'django.contrib.admin'
     verbose_name = _("administration")
 
     def ready(self):
+        checks.register('admin')(check_admin_app)
+
+
+class AdminConfig(SimpleAdminConfig):
+    """The default AppConfig for admin which does autodiscovery."""
+
+    def ready(self):
+        super(AdminConfig, self).ready()
         self.module.autodiscover()

+ 1 - 1
django/contrib/admin/sites.py

@@ -161,7 +161,7 @@ class AdminSite(object):
         installed, as well as the auth context processor.
         """
         if not apps.is_installed('django.contrib.admin'):
-            raise ImproperlyConfigured("Put 'django.contrib.admin.apps.AdminConfig' in "
+            raise ImproperlyConfigured("Put 'django.contrib.admin' in "
                 "your INSTALLED_APPS setting in order to use the admin application.")
         if not apps.is_installed('django.contrib.contenttypes'):
             raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in "

+ 1 - 0
django/contrib/admindocs/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.admindocs.apps.AdminDocsConfig'

+ 3 - 6
django/contrib/auth/__init__.py

@@ -2,8 +2,6 @@ import inspect
 import re
 
 from django.conf import settings
-from django.contrib.auth.checks import check_user_model
-from django.core import checks
 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
 from django.utils.module_loading import import_by_path
 from django.middleware.csrf import rotate_token
@@ -15,10 +13,6 @@ BACKEND_SESSION_KEY = '_auth_user_backend'
 REDIRECT_FIELD_NAME = 'next'
 
 
-# Register the user model checks
-checks.register('models')(check_user_model)
-
-
 def load_backend(path):
     return import_by_path(path)()
 
@@ -164,3 +158,6 @@ def get_permission_codename(action, opts):
     Returns the codename of the permission for the specified action.
     """
     return '%s_%s' % (action, opts.model_name)
+
+
+default_app_config = 'django.contrib.auth.apps.AuthConfig'

+ 5 - 0
django/contrib/auth/apps.py

@@ -1,4 +1,6 @@
 from django.apps import AppConfig
+from django.core import checks
+from django.contrib.auth.checks import check_user_model
 
 from django.utils.translation import ugettext_lazy as _
 
@@ -6,3 +8,6 @@ from django.utils.translation import ugettext_lazy as _
 class AuthConfig(AppConfig):
     name = 'django.contrib.auth'
     verbose_name = _("authentication and authorization")
+
+    def ready(self):
+        checks.register('models')(check_user_model)

+ 5 - 2
django/contrib/comments/__init__.py

@@ -1,6 +1,6 @@
 from importlib import import_module
 import warnings
-from django.apps import apps
+from django.apps import apps as django_apps
 from django.conf import settings
 from django.core import urlresolvers
 from django.core.exceptions import ImproperlyConfigured
@@ -16,7 +16,7 @@ def get_comment_app():
     Get the comment app (i.e. "django.contrib.comments") as defined in the settings
     """
     try:
-        app_config = apps.get_app_config(get_comment_app_name().rpartition(".")[2])
+        app_config = django_apps.get_app_config(get_comment_app_name().rpartition(".")[2])
     except LookupError:
         raise ImproperlyConfigured("The COMMENTS_APP (%r) "
                                    "must be in INSTALLED_APPS" % settings.COMMENTS_APP)
@@ -85,3 +85,6 @@ def get_approve_url(comment):
     else:
         return urlresolvers.reverse("django.contrib.comments.views.moderation.approve",
                                     args=(comment.id,))
+
+
+default_app_config = 'django.contrib.comments.apps.CommentsConfig'

+ 1 - 8
django/contrib/contenttypes/__init__.py

@@ -1,8 +1 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.contrib.contenttypes.checks import check_generic_foreign_keys
-from django.core import checks
-
-
-checks.register('models')(check_generic_foreign_keys)
+default_app_config = 'django.contrib.contenttypes.apps.ContentTypesConfig'

+ 5 - 1
django/contrib/contenttypes/apps.py

@@ -1,8 +1,12 @@
 from django.apps import AppConfig
-
+from django.contrib.contenttypes.checks import check_generic_foreign_keys
+from django.core import checks
 from django.utils.translation import ugettext_lazy as _
 
 
 class ContentTypesConfig(AppConfig):
     name = 'django.contrib.contenttypes'
     verbose_name = _("content types")
+
+    def ready(self):
+        checks.register('models')(check_generic_foreign_keys)

+ 1 - 0
django/contrib/flatpages/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.flatpages.apps.FlatPagesConfig'

+ 1 - 0
django/contrib/formtools/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.formtools.apps.FormToolsConfig'

+ 3 - 0
django/contrib/gis/__init__.py

@@ -4,3 +4,6 @@ if six.PY3:
     memoryview = memoryview
 else:
     memoryview = buffer
+
+
+default_app_config = 'django.contrib.gis.apps.GISConfig'

+ 1 - 0
django/contrib/humanize/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.humanize.apps.HumanizeConfig'

+ 3 - 0
django/contrib/messages/__init__.py

@@ -1,2 +1,5 @@
 from django.contrib.messages.api import *  # NOQA
 from django.contrib.messages.constants import *  # NOQA
+
+
+default_app_config = 'django.contrib.messages.apps.MessagesConfig'

+ 1 - 0
django/contrib/redirects/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.redirects.apps.RedirectsConfig'

+ 1 - 0
django/contrib/sessions/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.sessions.apps.SessionsConfig'

+ 3 - 0
django/contrib/sitemaps/__init__.py

@@ -133,3 +133,6 @@ class GenericSitemap(Sitemap):
         if self.date_field is not None:
             return getattr(item, self.date_field)
         return None
+
+
+default_app_config = 'django.contrib.sitemaps.apps.SiteMapsConfig'

+ 1 - 0
django/contrib/sites/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.sites.apps.SitesConfig'

+ 1 - 0
django/contrib/staticfiles/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.staticfiles.apps.StaticFilesConfig'

+ 1 - 0
django/contrib/syndication/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.syndication.apps.SyndicationConfig'

+ 1 - 0
django/contrib/webdesign/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'django.contrib.webdesign.apps.WebDesignConfig'

+ 2 - 1
django/core/checks/registry.py

@@ -29,7 +29,8 @@ class CheckRegistry(object):
 
         def inner(check):
             check.tags = tags
-            self.registered_checks.append(check)
+            if check not in self.registered_checks:
+                self.registered_checks.append(check)
             return check
 
         return inner

+ 1 - 8
docs/intro/tutorial01.txt

@@ -435,7 +435,7 @@ look like this:
     :filename: mysite/settings.py
 
     INSTALLED_APPS = (
-        'django.contrib.admin.apps.AdminConfig',
+        'django.contrib.admin',
         'django.contrib.auth',
         'django.contrib.contenttypes',
         'django.contrib.sessions',
@@ -444,13 +444,6 @@ look like this:
         'polls',
     )
 
-.. admonition:: Doesn't match what you see?
-
-    If you're seeing ``'django.contrib.admin'`` instead of
-    ``'django.contrib.admin.apps.AdminConfig'``, you're probably using a
-    version of Django that doesn't match this tutorial version.  You'll want
-    to either switch to the older tutorial or the newer Django version.
-
 Now Django knows to include the ``polls`` app. Let's run another command:
 
 .. code-block:: bash

+ 28 - 7
docs/ref/applications.txt

@@ -49,9 +49,15 @@ Configuring applications
 To configure an application, subclass :class:`~django.apps.AppConfig` and put
 the dotted path to that subclass in :setting:`INSTALLED_APPS`.
 
-Django uses the default :class:`~django.apps.AppConfig` class when
-:setting:`INSTALLED_APPS` simply contains the dotted path to an application
-module.
+When :setting:`INSTALLED_APPS` simply contains the dotted path to an
+application module, Django checks for a ``default_app_config`` variable in
+that module.
+
+If it's defined, it's the dotted path to the :class:`~django.apps.AppConfig`
+subclass for that application.
+
+If there is no ``default_app_config``, Django uses the base
+:class:`~django.apps.AppConfig` class.
 
 For application authors
 -----------------------
@@ -67,8 +73,23 @@ would provide a proper name for the admin::
         name = 'rock_n_roll'
         verbose_name = "Rock ’n’ roll"
 
-You would then tell your users to add ``'rock_n_roll.apps.RockNRollConfig'``
-to their :setting:`INSTALLED_APPS`.
+You can make your application load this :class:`~django.apps.AppConfig`
+subclass by default as follows::
+
+    # rock_n_roll/__init__.py
+
+    default_app_config = 'rock_n_roll.apps.RockNRollConfig'
+
+That will cause ``RockNRollConfig`` to be used when :setting:`INSTALLED_APPS`
+just contains ``'rock_n_roll'``. This allows you to make use of
+:class:`~django.apps.AppConfig` features without requiring your users to
+update their :setting:`INSTALLED_APPS` setting.
+
+Of course, you can also tell your users to put
+``'rock_n_roll.apps.RockNRollConfig'`` in their :setting:`INSTALLED_APPS`
+setting. You can even provide several different
+:class:`~django.apps.AppConfig` subclasses with different behaviors and allow
+your users to choose one via their :setting:`INSTALLED_APPS` setting.
 
 The recommended convention is to put the configuration class in a submodule of
 the application called ``apps``. However, this isn't enforced by Django.
@@ -87,7 +108,7 @@ configuration::
 
     # anthology/apps.py
 
-    from rock_n_roll.app import RockNRollConfig
+    from rock_n_roll.apps import RockNRollConfig
 
     class GypsyJazzConfig(RockNRollConfig):
         verbose_name = "Gypsy jazz"
@@ -213,7 +234,7 @@ Application registry
 .. method:: apps.is_installed(app_name)
 
     Checks whether an application with the given name exists in the registry.
-    ``app_name`` is the full name of the app, e.g. 'django.contrib.admin'.
+    ``app_name`` is the full name of the app, e.g. ``'django.contrib.admin'``.
 
     Unlike :meth:`~django.apps.apps.get_app_config`, this method can be called
     safely at import time. If the registry is still being populated, it may

+ 32 - 19
docs/ref/contrib/admin/index.txt

@@ -23,12 +23,7 @@ The admin is enabled in the default project template used by
 
 For reference, here are the requirements:
 
-1. Add ``'django.contrib.admin.apps.AdminConfig'`` to your
-   :setting:`INSTALLED_APPS` setting.
-
-   .. versionchanged:: 1.7
-
-       :setting:`INSTALLED_APPS` used to contain ``'django.contrib.admin'``.
+1. Add ``'django.contrib.admin'`` to your :setting:`INSTALLED_APPS` setting.
 
 2. The admin has four dependencies - :mod:`django.contrib.auth`,
    :mod:`django.contrib.contenttypes`,
@@ -136,16 +131,23 @@ The register decorator
 Discovery of admin files
 ------------------------
 
-The admin needs to be instructed to look for ``admin.py`` files in your project.
-The easiest solution is to put ``'django.contrib.admin.apps.AdminConfig'`` in
-your :setting:`INSTALLED_APPS` setting.
+When you put ``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS`
+setting, Django automatically looks for an ``admin`` module in each
+application and imports it.
 
 .. class:: apps.AdminConfig
 
     .. versionadded:: 1.7
 
-    This class calls :func:`~django.contrib.admin.autodiscover()` when Django
-    starts.
+    This is the default :class:`~django.apps.AppConfig` class for the admin.
+    It calls :func:`~django.contrib.admin.autodiscover()` when Django starts.
+
+.. class:: apps.SimpleAdminConfig
+
+    .. versionadded:: 1.7
+
+    This class works like :class:`~django.contrib.admin.apps.AdminConfig`,
+    except it doesn't call :func:`~django.contrib.admin.autodiscover()`.
 
 .. function:: autodiscover
 
@@ -155,13 +157,23 @@ your :setting:`INSTALLED_APPS` setting.
     .. versionchanged:: 1.7
 
         Previous versions of Django recommended calling this function directly
-        in the URLconf. :class:`~django.contrib.admin.apps.AdminConfig`
-        replaces that mechanism and is more robust.
+        in the URLconf. As of Django 1.7 this isn't needed anymore.
+        :class:`~django.contrib.admin.apps.AdminConfig` takes care of running
+        the auto-discovery automatically.
 
 If you are using a custom ``AdminSite``, it is common to import all of the
 ``ModelAdmin`` subclasses into your code and register them to the custom
-``AdminSite``. In that case, simply put ``'django.contrib.admin'`` in your
-:setting:`INSTALLED_APPS` setting, as you don't need autodiscovery.
+``AdminSite``. In that case, in order to disable auto-discovery, you should
+put ``'django.contrib.admin.apps.SimpleAdminConfig'`` instead of
+``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS` setting.
+
+.. versionchanged:: 1.7
+
+    In previous versions, the admin needed to be instructed to look for
+    ``admin.py`` files with :func:`~django.contrib.admin.autodiscover()`.
+    As of Django 1.7, auto-discovery is enabled by default and must be
+    explicitly disabled when it's undesirable.
+
 
 ``ModelAdmin`` options
 ----------------------
@@ -2426,11 +2438,12 @@ In this example, we register the ``AdminSite`` instance
         (r'^myadmin/', include(admin_site.urls)),
     )
 
-Note that you don't need autodiscovery of ``admin`` modules when using your
+Note that you may not want autodiscovery of ``admin`` modules when using your
 own ``AdminSite`` instance since you will likely be importing all the per-app
-``admin`` modules in your ``myproject.admin`` module. This means you likely do
-not need ``'django.contrib.admin.app.AdminConfig'`` in your
-:setting:`INSTALLED_APPS` and can just use ``'django.contrib.admin'``.
+``admin`` modules in your ``myproject.admin`` module. This means you need to
+put ``'django.contrib.admin.app.SimpleAdminConfig'`` instead of
+``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS` setting.
+
 
 Multiple admin sites in the same URLconf
 ----------------------------------------

+ 1 - 1
docs/ref/contrib/gis/tutorial.txt

@@ -115,7 +115,7 @@ In addition, modify the :setting:`INSTALLED_APPS` setting to include
 and ``world`` (your newly created application)::
 
     INSTALLED_APPS = (
-        'django.contrib.admin.apps.AdminConfig',
+        'django.contrib.admin',
         'django.contrib.auth',
         'django.contrib.contenttypes',
         'django.contrib.sessions',

+ 10 - 7
docs/releases/1.7.txt

@@ -89,17 +89,14 @@ Improvements thus far include:
 * The name of applications can be customized in the admin with the
   :attr:`~django.apps.AppConfig.verbose_name` of application configurations.
 
+* The admin automatically calls :func:`~django.contrib.admin.autodiscover()`
+  when Django starts. You can consequently remove this line from your
+  URLconf.
+
 * Django imports all application configurations and models as soon as it
   starts, through a deterministic and straightforward process. This should
   make it easier to diagnose import issues such as import loops.
 
-* The admin has an :class:`~django.contrib.admin.apps.AdminConfig` application
-  configuration class. When Django starts, this class takes care of calling
-  :func:`~django.contrib.admin.autodiscover()`. To use it, simply replace
-  ``'django.contrib.admin'`` in :setting:`INSTALLED_APPS` with
-  ``'django.contrib.admin.apps.AdminConfig'`` and remove
-  ``admin.autodiscover()`` from your URLconf.
-
 New method on Field subclasses
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -698,6 +695,12 @@ regressions cannot be ruled out. You may encounter the following exceptions:
   results. The code will be executed when you first need its results. This
   concept is known as "lazy evaluation".
 
+* ``django.contrib.admin`` will now automatically perform autodiscovery of
+  ``admin`` modules in installed applications. To prevent it, change your
+  :setting:`INSTALLED_APPS` to contain
+  ``'django.contrib.admin.apps.SimpleAdminConfig'`` instead of
+  ``'django.contrib.admin'``.
+
 Standalone scripts
 ^^^^^^^^^^^^^^^^^^
 

+ 2 - 3
docs/topics/auth/default.txt

@@ -67,9 +67,8 @@ Creating superusers
 -------------------
 
 :djadmin:`manage.py migrate <migrate>` prompts you to create a superuser the
-first time you run it with ``'django.contrib.auth'`` in your
-:setting:`INSTALLED_APPS`. If you need to create a superuser at a later date,
-you can use a command line utility::
+first time you run it with ``'django.contrib.auth'`` installed. If you need to
+create a superuser at a later date, you can use a command line utility::
 
     $ python manage.py createsuperuser --username=joe --email=joe@example.com
 

+ 1 - 1
tests/admin_scripts/tests.py

@@ -1113,7 +1113,7 @@ class ManageCheck(AdminScriptTestCase):
             apps=[
                 'admin_scripts.complex_app',
                 'admin_scripts.simple_app',
-                'django.contrib.admin',
+                'django.contrib.admin.apps.SimpleAdminConfig',
                 'django.contrib.auth',
                 'django.contrib.contenttypes',
             ],

+ 1 - 0
tests/apps/default_config_app/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'apps.default_config_app.apps.CustomConfig'

+ 5 - 0
tests/apps/default_config_app/apps.py

@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class CustomConfig(AppConfig):
+    name = 'apps.default_config_app'

+ 6 - 0
tests/apps/tests.py

@@ -7,6 +7,7 @@ from django.db import models
 from django.test import TestCase, override_settings
 from django.utils import six
 
+from .default_config_app.apps import CustomConfig
 from .models import TotallyNormal, SoAlternative, new_apps
 
 
@@ -82,6 +83,11 @@ class AppsTests(TestCase):
             with self.settings(INSTALLED_APPS=['apps.apps.NoSuchConfig']):
                 pass
 
+    def test_default_app_config(self):
+        with self.settings(INSTALLED_APPS=['apps.default_config_app']):
+            config = apps.get_app_config('default_config_app')
+        self.assertIsInstance(config, CustomConfig)
+
     @override_settings(INSTALLED_APPS=SOME_INSTALLED_APPS)
     def test_get_app_configs(self):
         """

+ 1 - 1
tests/i18n/tests.py

@@ -1049,7 +1049,7 @@ class AppResolutionOrderI18NTests(ResolutionOrderI18NTests):
             # Doesn't work because it's added later in the list.
             self.assertUgettext('Date/time', 'Datum/Zeit')
 
-            with self.modify_settings(INSTALLED_APPS={'remove': 'django.contrib.admin'}):
+            with self.modify_settings(INSTALLED_APPS={'remove': 'django.contrib.admin.apps.SimpleAdminConfig'}):
                 self.flush_caches()
                 activate('de')
 

+ 3 - 4
tests/runtests.py

@@ -44,7 +44,7 @@ ALWAYS_INSTALLED_APPS = [
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.comments',
-    'django.contrib.admin',
+    'django.contrib.admin.apps.SimpleAdminConfig',
     'django.contrib.admindocs',
     'django.contrib.staticfiles',
     'django.contrib.humanize',
@@ -168,12 +168,11 @@ def setup(verbosity, test_labels):
                 for label in test_labels_set)
 
         installed_app_names = set(get_installed())
-        if module_found_in_labels:
+        if module_found_in_labels and module_label not in installed_app_names:
             if verbosity >= 2:
                 print("Importing application %s" % module_name)
             # HACK.
-            if module_label not in installed_app_names:
-                settings.INSTALLED_APPS.append(module_label)
+            settings.INSTALLED_APPS.append(module_label)
             app_config = AppConfig.create(module_label)
             apps.app_configs[app_config.label] = app_config
             app_config.import_models(apps.all_models[app_config.label])