diff options
| author | Carl Meyer <carl@oddbird.net> | 2014-01-27 13:28:53 -0700 |
|---|---|---|
| committer | Carl Meyer <carl@oddbird.net> | 2014-01-27 15:34:22 -0700 |
| commit | 88a2d39159872f6a7fced1bae591550650fd7f38 (patch) | |
| tree | 0d7ee946c71354f8ea6095b549d926734b2e5579 /django/apps | |
| parent | b87bc461c89f2006f0b27c7240fb488fac32bed1 (diff) | |
Fixed #21874 -- Require Django applications to have a filesystem path.
Wherever possible this filesystem path is derived automatically from the app
module's ``__path__`` and ``__file__`` attributes (this avoids any
backwards-compatibility problems).
AppConfig allows specifying an app's filesystem location explicitly, which
overrides all autodetection based on ``__path__`` and ``__file__``. This
permits Django to support any type of module as an app (namespace packages,
fake modules, modules loaded by other hypothetical non-filesystem module
loaders), as long as the app is configured with an explicit filesystem path.
Thanks Aymeric for review and discussion.
Diffstat (limited to 'django/apps')
| -rw-r--r-- | django/apps/config.py | 43 |
1 files changed, 27 insertions, 16 deletions
diff --git a/django/apps/config.py b/django/apps/config.py index 1334da4c03..f7144505df 100644 --- a/django/apps/config.py +++ b/django/apps/config.py @@ -1,4 +1,5 @@ from importlib import import_module +import os from django.core.exceptions import ImproperlyConfigured from django.utils.module_loading import module_has_submodule @@ -34,23 +35,10 @@ class AppConfig(object): self.verbose_name = self.label.title() # Filesystem path to the application directory eg. - # u'/usr/lib/python2.7/dist-packages/django/contrib/admin'. May be - # None if the application isn't a bona fide package eg. if it's an - # egg. Otherwise it's a unicode on Python 2 and a str on Python 3. + # u'/usr/lib/python2.7/dist-packages/django/contrib/admin'. Unicode on + # Python 2 and a str on Python 3. if not hasattr(self, 'path'): - try: - paths = app_module.__path__ - except AttributeError: - self.path = None - else: - # Convert paths to list because Python 3.3 _NamespacePath does - # not support indexing. - paths = list(paths) - if len(paths) > 1: - raise ImproperlyConfigured( - "The namespace package app %r has multiple locations, " - "which is not supported: %r" % (app_name, paths)) - self.path = upath(paths[0]) + self.path = self._path_from_module(app_module) # Module containing models eg. <module 'django.contrib.admin.models' # from 'django/contrib/admin/models.pyc'>. Set by import_models(). @@ -64,6 +52,29 @@ class AppConfig(object): def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.label) + def _path_from_module(self, module): + """Attempt to determine app's filesystem path from its module.""" + # See #21874 for extended discussion of the behavior of this method in + # various cases. + # Convert paths to list because Python 3.3 _NamespacePath does not + # support indexing. + paths = list(getattr(module, '__path__', [])) + if len(paths) != 1: + filename = getattr(module, '__file__', None) + if filename is not None: + paths = [os.path.dirname(filename)] + if len(paths) > 1: + raise ImproperlyConfigured( + "The app module %r has multiple filesystem locations (%r); " + "you must configure this app with an AppConfig subclass " + "with a 'path' class attribute." % (module, paths)) + elif not paths: + raise ImproperlyConfigured( + "The app module %r has no filesystem location, " + "you must configure this app with an AppConfig subclass " + "with a 'path' class attribute." % (module,)) + return upath(paths[0]) + @classmethod def create(cls, entry): """ |
