Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Make it possible to force the use of a given AppConfig subclass. #2192

Closed
wants to merge 17 commits into from

5 participants

@mjtamlyn
Collaborator

No description provided.

@mjtamlyn mjtamlyn Make it possible to force the use of a given AppConfig subclass.
This behaviour is instantly deprecated, it's a migration aid not a new
feature.
4241608
@freakboy3742

I haven't downloaded and run the code, but from inspection, looks good to me.

Hang on - I take that back - have we lost the behaviour where an app that isn't installed via an explicit AppConfig, and doesn't have a default_app_config, is installed with a default AppConfig? (essentially, the last "else" statement in the old version) Raising this because this else case should probably raise a PendingDeprecationWarning as well, since we want to discourage non-AppConfig approaches.

Collaborator

No, it's lines 88-89 in the new version. I don't want to deprecate this - I think it's fine for apps which don't need any AppConfig related functionality to not provide an AppConfig. For example an "admin template overrides" app which doesn't need a ready() method. I don't think this should be deprecated - I don't believe that every single app should require an AppConfig, just every single app which has a use for them.

mjtamlyn added some commits
@mjtamlyn mjtamlyn Give the admin a default_app_config.
Also introduce a PlainAdminConfig which does not call autodiscover()
2e199ee
@mjtamlyn mjtamlyn Fixes #21831 -- Give auth a default_app_config.
This installs the checks on the user model. By putting this in an
AppConfig, it ensures that this does not get registered as a side effect
of importing auth.
1884c1b
@mjtamlyn
Collaborator

(first commit fixes https://code.djangoproject.com/ticket/21829 - I'll amend the commit message if this gets merged, but don't want to remove @freakboy3742's comments)

@mjtamlyn mjtamlyn commented on the diff
tests/runtests.py
@@ -171,8 +171,9 @@ def no_available_apps(self):
if module_found_in_labels:
if verbosity >= 2:
print("Importing application %s" % module_name)
- # HACK.
- if module_label not in installed_app_names:
+ if module_label in installed_app_names:
+ continue
@mjtamlyn Collaborator

I'm a little unsure about this change, but without it we end up with django.contrib.admin and django.contrib.admin.apps.PlainAdminConfig in the INSTALLED_APPS

@carljm Owner
carljm added a note

I'm not sure I understand how this change prevents that...

@mjtamlyn Collaborator

installed_app_names contains the names of the apps, which is not necessarily what is in INSTALLED_APPS. Because we now have django.contrib.admin.apps.PlainAdminConfig, and we look at contrib for places to find tests and pick up django.contrib.admin this resulted in trying to add it twice, and thus (in the old version) raising a warning. Whether this is still a problem is an interesting question, but it seemed wrong to me to be re-adding django.contrib.admin with a default AppConfig. As @aaugustin knows more about this I'd like him to look at the change

@carljm Owner
carljm added a note

Ok, I see. I was misunderstanding the implementation because I didn't notice that there was code after the 'else' that the 'continue' skipped.

@shaib Collaborator
shaib added a note

Actually, I find this a little misleading too. Why are lines 178--181 out of the else block?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mjtamlyn
Collaborator

I think we should only use default_app_config for the apps where we have a requirement for the AppConfig.ready(). Where it's a "nice to have" like in contrib.sites and just provides translation support for the admin, we do not need to force it.

This does however bring up an interesting question: if something else in contrib (or $THIRDPARTYAPP) wishes to convert to an AppConfig and need this "forced default" behaviour after Django 1.9 then we are potentially expecting them to push a breaking change as the app would now raise ImproperlyConfigured or similar when installed without the relevant AppConfig. Now personally I think this is ok, at least for third party, but it's worth mentioning.

So perhaps it's better to use default_app_config everywhere now?

Alternatively, we need to extend the idea of default_app_config to allow a by-app deprecation of installing it without the AppConfig. This does however mean that some third party apps may continue to rely on it for ever and not progress their deprecation, which would be bad. So the current plan is probably best IMO.

@carljm
Owner

As I said on the mailing list, this looks fine to me except that I think we should wait on the deprecation until we have some actual experience with the feature, rather than backing ourselves into a corner before we've even seen how the feature is used.

If we are explicitly allowing third-party apps that don't need AppConfig to not have one, indefinitely, then I think that is an even stronger argument why default_app_config should remain a permanent feature. Because an app may want to make that transition (from "no AppConfig" to "with AppConfig") anytime in the future, including apps that haven't even been written yet. So if we allow "no AppConfig" but remove default_app_config, we are saying in perpetuity that once you have released an app without an AppConfig, the only way you can start relying on one is by pushing a backwards-incompatible change on your users. To me that sounds like a great way to discourage use of AppConfig and make things unnecessarily difficult for app authors.

mjtamlyn added some commits
@mjtamlyn mjtamlyn Make CheckRegistry.register() idempotent.
I can't think of a good reason why it should be a problem if a check is
registered twice, but we are not going to want it to run twice and print
the errors twice.
f6a0e39
@mjtamlyn mjtamlyn Give contenttypes a default_app_config. c87ea33
@mjtamlyn mjtamlyn Remove the deprecation warnings, and make tests cleaner.
For now at least.
4f23640
@mjtamlyn mjtamlyn Make AdminConfig the default.
This is backwards incompatible if you do not normally use autodiscovery
and autodiscovery being called causes you errors. As far as I can tell,
if you do use autodiscovery autodiscover() being called twice does not
result in errors.
1cc45b6
@mjtamlyn mjtamlyn Add a note about default_app_config for app authors. 54b4017
@mjtamlyn
Collaborator

Ok, I've un-deprecated this feature and documented it. I have however recommended that explicit is better, though I have some personal reservations about that. For now, I've not added default_app_config to all contrib apps, which I think I probably should do.

I have also:

  • Made autodiscover-based AdminConfig the default, but provided a subclass which does not do this
  • Tweaked the checks framework to be idempotent (when INSTALLED_APPS kept changing in the test suite the checks kept getting added to)
django/contrib/admin/apps.py
((8 lines not shown))
self.module.autodiscover()
+
+ def install_checks(self):
+ checks.register('admin')(check_admin_app)
+
+
+class PlainAdminConfig(AdminConfig):
@carljm Owner
carljm added a note

This is fine, and I suppose it makes some sense to have the default admin app config be the base class - but in general I would go the other way and have the one that does less be the base class and the discovery one subclass and extend it. (Liskov something something mumble.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@carljm carljm commented on the diff
django/core/checks/registry.py
@@ -29,7 +29,8 @@ def my_check(apps, **kwargs):
def inner(check):
check.tags = tags
- self.registered_checks.append(check)
+ if check not in self.registered_checks:
@carljm Owner
carljm added a note

Under what conditions is this needed?

@mjtamlyn Collaborator

The specific situation I introduce it for is when INSTALLED_APPS is hacked around with so ready() gets called multiple times (whenever an app is added back in). In general though, if for some reason I accidentally register the same function twice, we shouldn't print the errors twice.

@carljm Owner
carljm added a note

Ah makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/ref/applications.txt
@@ -78,6 +78,14 @@ to determine which application this configuration applies to. You can define
any attributes documented in the :class:`~django.apps.AppConfig` API
reference.
+.. note::
+
+ It is recommended to tell users to add the full path to the ``AppConfig``
+ subclass. However, it is possible to force a particular default
@carljm Owner
carljm added a note

I think 'force' is misleading language here. It doesn't force anything, it's a fallback default. If a user wants to use a different AppConfig they can still refer to it explicitly in INSTALLED_APPS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/ref/applications.txt
@@ -78,6 +78,14 @@ to determine which application this configuration applies to. You can define
any attributes documented in the :class:`~django.apps.AppConfig` API
reference.
+.. note::
+
+ It is recommended to tell users to add the full path to the ``AppConfig``
@carljm Owner
carljm added a note

Personally I would not say this is recommended; I think using default_app_config is a fine option, and users can be explicit only if they need a nonstandard AppConfig. I think we should at least be consistent - if we recommend long-form as preferable, we should use it throughout docs/examples/project-template etc; if we're not doing it ourselves, no point in claiming that we recommend it.

@mjtamlyn Collaborator

I have the same conflict of feeling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/ref/contrib/gis/tutorial.txt
@@ -116,7 +116,7 @@ and ``world`` (your newly created application)::
INSTALLED_APPS = (
'django.contrib.admin.apps.AdminConfig',
- 'django.contrib.auth',
+ 'django.contrib.auth.apps.AuthConfig',
@carljm Owner
carljm added a note

This change seems to be going in the opposite direction from the rest of the PR (i.e. you removed a bunch of long-form usage for admin, so why are we changing this to long-form usage for auth)?

@mjtamlyn Collaborator

Well spotted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/ref/applications.txt
@@ -78,6 +78,12 @@ to determine which application this configuration applies to. You can define
any attributes documented in the :class:`~django.apps.AppConfig` API
reference.
+.. note::
+
+ It is possible to ensure a particular default ``AppConfig`` subclass by
@carljm Owner
carljm added a note

I think the wording of this note is a bit unclear, as it doesn't clarify which __init__.py, and doesn't really explain what it means to "ensure a particular default ...". I think it would be clearer if default_app_config were mentioned further up, right after the code block, in the main flow of the text rather than in a note. Something like:

You can make this AppConfig subclass the default for your app by setting
``default_app_config = "rock_n_roll.apps.RockNRollConfig"`` in ``rocknroll/__init__.py`` --
that will cause this ``AppConfig`` to be used if a user has just ``"rocknroll"`` in their 
INSTALLED_APPS. Alternatively, you can tell your users to put
``"rocknroll.apps.RockNRollConfig"`` in their INSTALLED_APPS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@freakboy3742 freakboy3742 commented on the diff
django/contrib/admin/apps.py
((5 lines not shown))
from django.utils.translation import ugettext_lazy as _
-class AdminConfig(AppConfig):
+class PlainAdminConfig(AppConfig):
+ """Simple AppConfig which does not do automatic discovery."""
@freakboy3742 Owner

100% bike shedding, but given the docstring, SimpleAdminConfig strikes me as a better name here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@freakboy3742

Updated review now that all the ready() code is in place -- the code looks good for what it's trying to do. My only concern is the amount to which it rolls back the "explicit AppConfig" angle in INSTALLED_APPS. Personally, I think I can live with the patch as is, but I think our community discussion is getting a bit fractured and I'm not convinced we're still all on the same page (or, at least, some seem to be a couple of pages behind).

django/apps/base.py
((17 lines not shown))
- # Raise the original exception when entry cannot be a path to an
- # app config class.
- if not mod_path:
- raise
+ if module is not None:
@shaib Collaborator
shaib added a note

Style nitpick: use "else:" here instead of the If. Then you also don't need to erase module in the except.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
django/apps/base.py
((53 lines not shown))
- # 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_path, _, cls_name = entry.rpartition('.')
+
+ # Raise the original exception when entry cannot be a path to an
+ # app config class.
+ if not mod_path:
@shaib Collaborator
shaib added a note

if not (mod_path and cls_name) for the case where entry ends with a ".".

@mjtamlyn Collaborator

I see your thinking, but when I tried this what actually happens in this case for now is it drops down to the following lines and you get (for path.to.:

ImportError: cannot import '' from 'path.to' which is actually pretty informative I think, as opposed to cannot import "path.to.".

@shaib Collaborator
shaib added a note

Yes, I guess you're right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@shaib shaib commented on the diff
django/apps/base.py
((61 lines not shown))
+ # app config class.
+ if not mod_path:
+ raise exception
+
+ 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.)
@shaib Collaborator
shaib added a note

Use ABC?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/releases/1.7.txt
@@ -95,10 +95,11 @@ Improvements thus far include:
* 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.
+ :func:`~django.contrib.admin.autodiscover()`. You can consequently remove the
+ line from your ``urls.py``. If you do not wish to use the ``autodiscover()``
+ behaviour, simply, simply replace ``'django.contrib.admin'`` in
@shaib Collaborator
shaib added a note

simply use just one simply :)
also, behavior; I think Django docs standardize on US spelling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@shaib shaib commented on the diff
django/apps/base.py
((58 lines not shown))
- # Entry is a path to an app config class.
- return cls(app_name, app_module)
+ # Raise the original exception when entry cannot be a path to an
+ # app config class.
+ if not mod_path:
+ raise exception
@shaib Collaborator
shaib added a note

IIUC, writing default_app_config="" in your __init__.py will get this code to do raise None (there is no exception raised from importing the module, and yet mod_path is falsey).

@aaugustin Owner

Reraising an exception like this doesn't work perfectly. See six.reraise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@aaugustin aaugustin commented on the diff
tests/runtests.py
@@ -171,8 +171,9 @@ def no_available_apps(self):
if module_found_in_labels:
if verbosity >= 2:
print("Importing application %s" % module_name)
- # HACK.
@aaugustin Owner

Why does everyone keep removing those carefully crafted comments? ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@aaugustin aaugustin commented on the diff
docs/ref/contrib/admin/index.txt
@@ -137,8 +132,8 @@ 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.
+By default, this happens automatically when you put
+``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS` setting.
@aaugustin Owner

This sentence contradicts the previous one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@aaugustin
Owner

Updated patch: #2210

I still have to review all the comments here and to proof-read the docs.

@aaugustin aaugustin closed this
@mjtamlyn mjtamlyn deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 21, 2014
  1. @mjtamlyn

    Make it possible to force the use of a given AppConfig subclass.

    mjtamlyn authored
    This behaviour is instantly deprecated, it's a migration aid not a new
    feature.
  2. @mjtamlyn

    Give the admin a default_app_config.

    mjtamlyn authored
    Also introduce a PlainAdminConfig which does not call autodiscover()
  3. @mjtamlyn

    Fixes #21831 -- Give auth a default_app_config.

    mjtamlyn authored
    This installs the checks on the user model. By putting this in an
    AppConfig, it ensures that this does not get registered as a side effect
    of importing auth.
  4. @mjtamlyn

    Make CheckRegistry.register() idempotent.

    mjtamlyn authored
    I can't think of a good reason why it should be a problem if a check is
    registered twice, but we are not going to want it to run twice and print
    the errors twice.
  5. @mjtamlyn
  6. @mjtamlyn
  7. @mjtamlyn

    Make AdminConfig the default.

    mjtamlyn authored
    This is backwards incompatible if you do not normally use autodiscovery
    and autodiscovery being called causes you errors. As far as I can tell,
    if you do use autodiscovery autodiscover() being called twice does not
    result in errors.
  8. @mjtamlyn
  9. @mjtamlyn

    force -> ensure.

    mjtamlyn authored
  10. @mjtamlyn

    Be consistent!

    mjtamlyn authored
  11. @mjtamlyn

    Let's not recommend anything.

    mjtamlyn authored
  12. @mjtamlyn
  13. @mjtamlyn

    Blah blah Liskov blah.

    mjtamlyn authored
Commits on Jan 23, 2014
  1. @mjtamlyn

    Better logical flow.

    mjtamlyn authored
  2. @mjtamlyn

    Simply fix a simple typo.

    mjtamlyn authored
  3. @mjtamlyn

    Rewrite docs for default_app_config.

    mjtamlyn authored
    Thanks to Carl for words which make sense.
  4. @mjtamlyn

    US spelling.

    mjtamlyn authored
This page is out of date. Refresh to see the latest.
View
84 django/apps/base.py
@@ -60,54 +60,62 @@ def create(cls, entry):
"""
Factory that creates an app config from an entry in INSTALLED_APPS.
"""
+ exception = None
try:
# If import_module succeeds, entry is a path to an app module.
# 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('.')
+ except ImportError as e:
+ exception = e
- # Raise the original exception when entry cannot be a path to an
- # app config class.
- if not mod_path:
- raise
+ else:
+ if hasattr(module, 'default_app_config'):
+ entry = module.default_app_config
+ else:
+ # Entry is a path to an app module, use the default config
+ return cls(entry, module)
- 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)
+ # We now have a dotted path to an AppConfig class
- # 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_path, _, cls_name = entry.rpartition('.')
- # Entry is a path to an app config class.
- return cls(app_name, app_module)
+ # Raise the original exception when entry cannot be a path to an
+ # app config class.
+ if not mod_path:
+ raise exception
@shaib Collaborator
shaib added a note

IIUC, writing default_app_config="" in your __init__.py will get this code to do raise None (there is no exception raised from importing the module, and yet mod_path is falsey).

@aaugustin Owner

Reraising an exception like this doesn't work perfectly. See six.reraise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
- else:
- # Entry is a path to an app module.
- return cls(entry, module)
+ 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.)
@shaib Collaborator
shaib added a note

Use ABC?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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)
+
+ # 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)
def get_model(self, model_name):
"""
View
2  django/conf/project_template/project_name/settings.py
@@ -30,7 +30,7 @@
# Application definition
INSTALLED_APPS = (
- 'django.contrib.admin.apps.AdminConfig',
+ 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
View
5 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 @@
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 @@
def autodiscover():
autodiscover_modules('admin', register_to=site)
-checks.register('admin')(check_admin_app)
+
+default_app_config = 'django.contrib.admin.apps.AdminConfig'
View
15 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 PlainAdminConfig(AppConfig):
+ """Simple AppConfig which does not do automatic discovery."""
@freakboy3742 Owner

100% bike shedding, but given the docstring, SimpleAdminConfig strikes me as a better name here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
name = 'django.contrib.admin'
verbose_name = _("administration")
def ready(self):
+ checks.register('admin')(check_admin_app)
+
+
+class AdminConfig(PlainAdminConfig):
+ """The default AppConfig for admin which does autodiscovery."""
+
+ def ready(self):
+ super(AdminConfig, self).ready()
self.module.autodiscover()
View
9 django/contrib/auth/__init__.py
@@ -2,8 +2,6 @@
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 @@
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'
View
5 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 @@
class AuthConfig(AppConfig):
name = 'django.contrib.auth'
verbose_name = _("authentication and authorization")
+
+ def ready(self):
+ checks.register('models')(check_user_model)
View
9 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'
View
6 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)
View
3  django/core/checks/registry.py
@@ -29,7 +29,8 @@ def my_check(apps, **kwargs):
def inner(check):
check.tags = tags
- self.registered_checks.append(check)
+ if check not in self.registered_checks:
@carljm Owner
carljm added a note

Under what conditions is this needed?

@mjtamlyn Collaborator

The specific situation I introduce it for is when INSTALLED_APPS is hacked around with so ready() gets called multiple times (whenever an app is added back in). In general though, if for some reason I accidentally register the same function twice, we shouldn't print the errors twice.

@carljm Owner
carljm added a note

Ah makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ self.registered_checks.append(check)
return check
return inner
View
9 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
View
9 docs/ref/applications.txt
@@ -78,6 +78,15 @@ to determine which application this configuration applies to. You can define
any attributes documented in the :class:`~django.apps.AppConfig` API
reference.
+.. note::
+
+ You can make this AppConfig subclass the default for your app by setting
+ ``default_app_config = 'rock_n_roll.apps.RockNRollConfig'`` in
+ ``rocknroll/__init__.py`` -- that will cause this ``AppConfig`` to be used
+ if a user has just ``"rocknroll"`` in their ``INSTALLED_APPS``. Alternatively,
+ you can tell your users to put ``"rocknroll.apps.RockNRollConfig"`` in
+ their ``INSTALLED_APPS``.
+
For application users
---------------------
View
30 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`,
@@ -137,8 +132,8 @@ 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.
+By default, this happens automatically when you put
+``'django.contrib.admin'`` in your :setting:`INSTALLED_APPS` setting.
@aaugustin Owner

This sentence contradicts the previous one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.. class:: apps.AdminConfig
@@ -147,6 +142,14 @@ your :setting:`INSTALLED_APPS` setting.
This class calls :func:`~django.contrib.admin.autodiscover()` when Django
starts.
+.. class:: apps.PlainAdminConfig
+
+ .. versionadded:: 1.7
+
+ This class performs other work done by `AdminConfig`, such as registering
+ checks and providing a translatable verbose name, but does not call
+ ``autodiscover()``.
+
.. function:: autodiscover
This function attempts to import an ``admin`` module in each installed
@@ -160,7 +163,8 @@ your :setting:`INSTALLED_APPS` setting.
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
+``AdminSite``. In that case, put
+``'django.contrib.admin.apps.PlainAdminConfig'`` in your
:setting:`INSTALLED_APPS` setting, as you don't need autodiscovery.
``ModelAdmin`` options
@@ -2426,11 +2430,11 @@ 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 don't 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
+change your :setting:`INSTALLED_APPS` to
+``'django.contrib.admin.app.PlainAdminConfig'``.
Multiple admin sites in the same URLconf
----------------------------------------
View
2  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',
View
14 docs/releases/1.7.txt
@@ -95,10 +95,11 @@ Improvements thus far include:
* 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.
+ :func:`~django.contrib.admin.autodiscover()`. You can consequently remove the
+ line from your ``urls.py``. If you do not wish to use the ``autodiscover()``
+ behavior, simply replace ``'django.contrib.admin'`` in
+ :setting:`INSTALLED_APPS` with
+ :class:`django.contrib.admin.apps.PlainAdminConfig`.
New method on Field subclasses
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -698,6 +699,11 @@ 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 if you
+ do not specifically set your :setting:`INSTALLED_APPS` to contain
+ :class:`django.contrib.admin.apps.PlainAdminConfig` instead of
+ ``django.contrib.admin``.
+
Standalone scripts
^^^^^^^^^^^^^^^^^^
View
5 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
View
2  tests/admin_scripts/tests.py
@@ -1113,7 +1113,7 @@ def test_complex_app(self):
apps=[
'admin_scripts.complex_app',
'admin_scripts.simple_app',
- 'django.contrib.admin',
+ 'django.contrib.admin.apps.PlainAdminConfig',
'django.contrib.auth',
'django.contrib.contenttypes',
],
View
1  tests/apps/default_config_app/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'apps.default_config_app.apps.CustomConfig'
View
5 tests/apps/default_config_app/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class CustomConfig(AppConfig):
+ name = 'apps.default_config_app'
View
6 tests/apps/tests.py
@@ -7,6 +7,7 @@
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 @@ def test_no_such_app_config(self):
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):
"""
View
2  tests/i18n/tests.py
@@ -1049,7 +1049,7 @@ def test_app_translation(self):
# 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.PlainAdminConfig'}):
self.flush_caches()
activate('de')
View
7 tests/runtests.py
@@ -44,7 +44,7 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.comments',
- 'django.contrib.admin',
+ 'django.contrib.admin.apps.PlainAdminConfig',
'django.contrib.admindocs',
'django.contrib.staticfiles',
'django.contrib.humanize',
@@ -171,8 +171,9 @@ def no_available_apps(self):
if module_found_in_labels:
if verbosity >= 2:
print("Importing application %s" % module_name)
- # HACK.
@aaugustin Owner

Why does everyone keep removing those carefully crafted comments? ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
- if module_label not in installed_app_names:
+ if module_label in installed_app_names:
+ continue
@mjtamlyn Collaborator

I'm a little unsure about this change, but without it we end up with django.contrib.admin and django.contrib.admin.apps.PlainAdminConfig in the INSTALLED_APPS

@carljm Owner
carljm added a note

I'm not sure I understand how this change prevents that...

@mjtamlyn Collaborator

installed_app_names contains the names of the apps, which is not necessarily what is in INSTALLED_APPS. Because we now have django.contrib.admin.apps.PlainAdminConfig, and we look at contrib for places to find tests and pick up django.contrib.admin this resulted in trying to add it twice, and thus (in the old version) raising a warning. Whether this is still a problem is an interesting question, but it seemed wrong to me to be re-adding django.contrib.admin with a default AppConfig. As @aaugustin knows more about this I'd like him to look at the change

@carljm Owner
carljm added a note

Ok, I see. I was misunderstanding the implementation because I didn't notice that there was code after the 'else' that the 'continue' skipped.

@shaib Collaborator
shaib added a note

Actually, I find this a little misleading too. Why are lines 178--181 out of the else block?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ else:
settings.INSTALLED_APPS.append(module_label)
app_config = AppConfig.create(module_label)
apps.app_configs[app_config.label] = app_config
Something went wrong with that request. Please try again.