Made it possible to create apps without a models module.
This commit revertsf44c4a5d0f
and39bbd165
. django.test.simple will be updated in a separate commit as it requires invasive changes.
This commit is contained in:
parent
69039becde
commit
5ba743e262
|
@ -22,7 +22,8 @@ class AppConfig(object):
|
||||||
self.app_module = app_module
|
self.app_module = app_module
|
||||||
|
|
||||||
# Module containing models eg. <module 'django.contrib.admin.models'
|
# Module containing models eg. <module 'django.contrib.admin.models'
|
||||||
# from 'django/contrib/admin/models.pyc'>.
|
# from 'django/contrib/admin/models.pyc'>. None if the application
|
||||||
|
# doesn't have a models module.
|
||||||
self.models_module = models_module
|
self.models_module = models_module
|
||||||
|
|
||||||
# Mapping of lower case model names to model classes.
|
# Mapping of lower case model names to model classes.
|
||||||
|
|
|
@ -88,7 +88,7 @@ class BaseAppCache(object):
|
||||||
for app_name in settings.INSTALLED_APPS:
|
for app_name in settings.INSTALLED_APPS:
|
||||||
if app_name in self.handled:
|
if app_name in self.handled:
|
||||||
continue
|
continue
|
||||||
self.load_app(app_name, True)
|
self.load_app(app_name, can_postpone=True)
|
||||||
if not self.nesting_level:
|
if not self.nesting_level:
|
||||||
for app_name in self.postponed:
|
for app_name in self.postponed:
|
||||||
self.load_app(app_name)
|
self.load_app(app_name)
|
||||||
|
@ -115,10 +115,10 @@ class BaseAppCache(object):
|
||||||
models_module = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME))
|
models_module = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME))
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.nesting_level -= 1
|
self.nesting_level -= 1
|
||||||
# If the app doesn't have a models module, we can just ignore the
|
# If the app doesn't have a models module, we can just swallow the
|
||||||
# ImportError and return no models for it.
|
# ImportError and return no models for this app.
|
||||||
if not module_has_submodule(app_module, MODELS_MODULE_NAME):
|
if not module_has_submodule(app_module, MODELS_MODULE_NAME):
|
||||||
return None
|
models_module = None
|
||||||
# But if the app does have a models module, we need to figure out
|
# But if the app does have a models module, we need to figure out
|
||||||
# whether to suppress or propagate the error. If can_postpone is
|
# whether to suppress or propagate the error. If can_postpone is
|
||||||
# True then it may be that the package is still being imported by
|
# True then it may be that the package is still being imported by
|
||||||
|
@ -129,7 +129,7 @@ class BaseAppCache(object):
|
||||||
else:
|
else:
|
||||||
if can_postpone:
|
if can_postpone:
|
||||||
self.postponed.append(app_name)
|
self.postponed.append(app_name)
|
||||||
return None
|
return
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@ -154,22 +154,27 @@ class BaseAppCache(object):
|
||||||
"""
|
"""
|
||||||
return self.loaded
|
return self.loaded
|
||||||
|
|
||||||
def get_app_configs(self, only_installed=True):
|
def get_app_configs(self, only_installed=True, only_with_models_module=False):
|
||||||
"""
|
"""
|
||||||
Return an iterable of application configurations.
|
Return an iterable of application configurations.
|
||||||
|
|
||||||
If only_installed is True (default), only applications explicitly
|
If only_installed is True (default), only applications explicitly
|
||||||
listed in INSTALLED_APPS are considered.
|
listed in INSTALLED_APPS are considered.
|
||||||
|
|
||||||
|
If only_with_models_module in True (non-default), only applications
|
||||||
|
containing a models module are considered.
|
||||||
"""
|
"""
|
||||||
self.populate()
|
self.populate()
|
||||||
for app_config in self.app_configs.values():
|
for app_config in self.app_configs.values():
|
||||||
if only_installed and not app_config.installed:
|
if only_installed and not app_config.installed:
|
||||||
continue
|
continue
|
||||||
|
if only_with_models_module and app_config.models_module is None:
|
||||||
|
continue
|
||||||
if self.available_apps is not None and app_config.name not in self.available_apps:
|
if self.available_apps is not None and app_config.name not in self.available_apps:
|
||||||
continue
|
continue
|
||||||
yield app_config
|
yield app_config
|
||||||
|
|
||||||
def get_app_config(self, app_label, only_installed=True):
|
def get_app_config(self, app_label, only_installed=True, only_with_models_module=False):
|
||||||
"""
|
"""
|
||||||
Returns the application configuration for the given app_label.
|
Returns the application configuration for the given app_label.
|
||||||
|
|
||||||
|
@ -180,11 +185,18 @@ class BaseAppCache(object):
|
||||||
|
|
||||||
If only_installed is True (default), only applications explicitly
|
If only_installed is True (default), only applications explicitly
|
||||||
listed in INSTALLED_APPS are considered.
|
listed in INSTALLED_APPS are considered.
|
||||||
|
|
||||||
|
If only_with_models_module in True (non-default), only applications
|
||||||
|
containing a models module are considered.
|
||||||
"""
|
"""
|
||||||
self.populate()
|
self.populate()
|
||||||
app_config = self.app_configs.get(app_label)
|
app_config = self.app_configs.get(app_label)
|
||||||
if app_config is None or (only_installed and not app_config.installed):
|
if app_config is None:
|
||||||
raise LookupError("No app with label %r." % app_label)
|
raise LookupError("No app with label %r." % app_label)
|
||||||
|
if only_installed and not app_config.installed:
|
||||||
|
raise LookupError("App with label %r isn't in INSTALLED_APPS." % app_label)
|
||||||
|
if only_with_models_module and app_config.models_module is None:
|
||||||
|
raise LookupError("App with label %r doesn't have a models module." % app_label)
|
||||||
if self.available_apps is not None and app_config.name not in self.available_apps:
|
if self.available_apps is not None and app_config.name not in self.available_apps:
|
||||||
raise UnavailableApp("App with label %r isn't available." % app_label)
|
raise UnavailableApp("App with label %r isn't available." % app_label)
|
||||||
return app_config
|
return app_config
|
||||||
|
|
|
@ -86,7 +86,7 @@ If you're unsure, answer 'no'.
|
||||||
|
|
||||||
|
|
||||||
def update_all_contenttypes(verbosity=2, **kwargs):
|
def update_all_contenttypes(verbosity=2, **kwargs):
|
||||||
for app_config in app_cache.get_app_configs():
|
for app_config in app_cache.get_app_configs(only_with_models_module=True):
|
||||||
update_contenttypes(app_config.models_module, None, verbosity, **kwargs)
|
update_contenttypes(app_config.models_module, None, verbosity, **kwargs)
|
||||||
|
|
||||||
signals.post_migrate.connect(update_contenttypes)
|
signals.post_migrate.connect(update_contenttypes)
|
||||||
|
|
|
@ -345,12 +345,16 @@ class AppCommand(BaseCommand):
|
||||||
if not app_labels:
|
if not app_labels:
|
||||||
raise CommandError('Enter at least one appname.')
|
raise CommandError('Enter at least one appname.')
|
||||||
try:
|
try:
|
||||||
app_list = [app_cache.get_app_config(app_label).models_module for app_label in app_labels]
|
app_configs = [app_cache.get_app_config(app_label) for app_label in app_labels]
|
||||||
except (LookupError, ImportError) as e:
|
except (LookupError, ImportError) as e:
|
||||||
raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
|
raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
|
||||||
output = []
|
output = []
|
||||||
for app in app_list:
|
for app_config in app_configs:
|
||||||
app_output = self.handle_app(app, **options)
|
if app_config.models_module is None:
|
||||||
|
raise CommandError(
|
||||||
|
"AppCommand cannot handle app %r because it doesn't have "
|
||||||
|
"a models module." % app_config.label)
|
||||||
|
app_output = self.handle_app(app_config.models_module, **options)
|
||||||
if app_output:
|
if app_output:
|
||||||
output.append(app_output)
|
output.append(app_output)
|
||||||
return '\n'.join(output)
|
return '\n'.join(output)
|
||||||
|
|
|
@ -70,6 +70,7 @@ class Command(BaseCommand):
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
app_obj = app_cache.get_app_config(exclude).models_module
|
app_obj = app_cache.get_app_config(exclude).models_module
|
||||||
|
if app_obj is not None:
|
||||||
excluded_apps.add(app_obj)
|
excluded_apps.add(app_obj)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise CommandError('Unknown app in excludes: %s' % exclude)
|
raise CommandError('Unknown app in excludes: %s' % exclude)
|
||||||
|
@ -78,7 +79,7 @@ class Command(BaseCommand):
|
||||||
if primary_keys:
|
if primary_keys:
|
||||||
raise CommandError("You can only use --pks option with one model")
|
raise CommandError("You can only use --pks option with one model")
|
||||||
app_list = OrderedDict((app_config.models_module, None)
|
app_list = OrderedDict((app_config.models_module, None)
|
||||||
for app_config in app_cache.get_app_configs()
|
for app_config in app_cache.get_app_configs(only_with_models_module=True)
|
||||||
if app_config.models_module not in excluded_apps)
|
if app_config.models_module not in excluded_apps)
|
||||||
else:
|
else:
|
||||||
if len(app_labels) > 1 and primary_keys:
|
if len(app_labels) > 1 and primary_keys:
|
||||||
|
@ -91,7 +92,7 @@ class Command(BaseCommand):
|
||||||
app = app_cache.get_app_config(app_label).models_module
|
app = app_cache.get_app_config(app_label).models_module
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise CommandError("Unknown application: %s" % app_label)
|
raise CommandError("Unknown application: %s" % app_label)
|
||||||
if app in excluded_apps:
|
if app is None or app in excluded_apps:
|
||||||
continue
|
continue
|
||||||
model = app_cache.get_model(app_label, model_label)
|
model = app_cache.get_model(app_label, model_label)
|
||||||
if model is None:
|
if model is None:
|
||||||
|
@ -111,7 +112,7 @@ class Command(BaseCommand):
|
||||||
app = app_cache.get_app_config(app_label).models_module
|
app = app_cache.get_app_config(app_label).models_module
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise CommandError("Unknown application: %s" % app_label)
|
raise CommandError("Unknown application: %s" % app_label)
|
||||||
if app in excluded_apps:
|
if app is None or app in excluded_apps:
|
||||||
continue
|
continue
|
||||||
app_list[app] = None
|
app_list[app] = None
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,6 @@ Are you sure you want to do this?
|
||||||
# Emit the post migrate signal. This allows individual applications to
|
# Emit the post migrate signal. This allows individual applications to
|
||||||
# respond as if the database had been migrated from scratch.
|
# respond as if the database had been migrated from scratch.
|
||||||
all_models = []
|
all_models = []
|
||||||
for app_config in app_cache.get_app_configs():
|
for app_config in app_cache.get_app_configs(only_with_models_module=True):
|
||||||
all_models.extend(router.get_migratable_models(app_config.models_module, database, include_auto_created=True))
|
all_models.extend(router.get_migratable_models(app_config.models_module, database, include_auto_created=True))
|
||||||
emit_post_migrate_signal(set(all_models), verbosity, interactive, database)
|
emit_post_migrate_signal(set(all_models), verbosity, interactive, database)
|
||||||
|
|
|
@ -182,7 +182,7 @@ class Command(BaseCommand):
|
||||||
all_models = [
|
all_models = [
|
||||||
(app_config.label,
|
(app_config.label,
|
||||||
router.get_migratable_models(app_config.models_module, connection.alias, include_auto_created=True))
|
router.get_migratable_models(app_config.models_module, connection.alias, include_auto_created=True))
|
||||||
for app_config in app_cache.get_app_configs()
|
for app_config in app_cache.get_app_configs(only_with_models_module=True)
|
||||||
if app_config.label in apps
|
if app_config.label in apps
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,7 @@ def custom_sql_for_model(model, style, connection):
|
||||||
|
|
||||||
def emit_pre_migrate_signal(create_models, verbosity, interactive, db):
|
def emit_pre_migrate_signal(create_models, verbosity, interactive, db):
|
||||||
# Emit the pre_migrate signal for every application.
|
# Emit the pre_migrate signal for every application.
|
||||||
for app_config in app_cache.get_app_configs():
|
for app_config in app_cache.get_app_configs(only_with_models_module=True):
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
print("Running pre-migrate handlers for application %s" % app_config.label)
|
print("Running pre-migrate handlers for application %s" % app_config.label)
|
||||||
models.signals.pre_migrate.send(
|
models.signals.pre_migrate.send(
|
||||||
|
@ -221,7 +221,7 @@ def emit_pre_migrate_signal(create_models, verbosity, interactive, db):
|
||||||
|
|
||||||
def emit_post_migrate_signal(created_models, verbosity, interactive, db):
|
def emit_post_migrate_signal(created_models, verbosity, interactive, db):
|
||||||
# Emit the post_migrate signal for every application.
|
# Emit the post_migrate signal for every application.
|
||||||
for app_config in app_cache.get_app_configs():
|
for app_config in app_cache.get_app_configs(only_with_models_module=True):
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
print("Running post-migrate handlers for application %s" % app_config.label)
|
print("Running post-migrate handlers for application %s" % app_config.label)
|
||||||
models.signals.post_migrate.send(
|
models.signals.post_migrate.send(
|
||||||
|
|
|
@ -1271,7 +1271,7 @@ class BaseDatabaseIntrospection(object):
|
||||||
from django.apps import app_cache
|
from django.apps import app_cache
|
||||||
from django.db import router
|
from django.db import router
|
||||||
tables = set()
|
tables = set()
|
||||||
for app_config in app_cache.get_app_configs():
|
for app_config in app_cache.get_app_configs(only_with_models_module=True):
|
||||||
for model in router.get_migratable_models(app_config.models_module, self.connection.alias):
|
for model in router.get_migratable_models(app_config.models_module, self.connection.alias):
|
||||||
if not model._meta.managed:
|
if not model._meta.managed:
|
||||||
continue
|
continue
|
||||||
|
@ -1292,7 +1292,7 @@ class BaseDatabaseIntrospection(object):
|
||||||
from django.apps import app_cache
|
from django.apps import app_cache
|
||||||
from django.db import router
|
from django.db import router
|
||||||
all_models = []
|
all_models = []
|
||||||
for app_config in app_cache.get_app_configs():
|
for app_config in app_cache.get_app_configs(only_with_models_module=True):
|
||||||
all_models.extend(router.get_migratable_models(app_config.models_module, self.connection.alias))
|
all_models.extend(router.get_migratable_models(app_config.models_module, self.connection.alias))
|
||||||
tables = list(map(self.table_name_converter, tables))
|
tables = list(map(self.table_name_converter, tables))
|
||||||
return set([
|
return set([
|
||||||
|
@ -1307,7 +1307,7 @@ class BaseDatabaseIntrospection(object):
|
||||||
|
|
||||||
sequence_list = []
|
sequence_list = []
|
||||||
|
|
||||||
for app_config in app_cache.get_app_configs():
|
for app_config in app_cache.get_app_configs(only_with_models_module=True):
|
||||||
for model in router.get_migratable_models(app_config.models_module, self.connection.alias):
|
for model in router.get_migratable_models(app_config.models_module, self.connection.alias):
|
||||||
if not model._meta.managed:
|
if not model._meta.managed:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -55,7 +55,7 @@ class MigrationLoader(object):
|
||||||
self.disk_migrations = {}
|
self.disk_migrations = {}
|
||||||
self.unmigrated_apps = set()
|
self.unmigrated_apps = set()
|
||||||
self.migrated_apps = set()
|
self.migrated_apps = set()
|
||||||
for app_config in app_cache.get_app_configs():
|
for app_config in app_cache.get_app_configs(only_with_models_module=True):
|
||||||
# Get the migrations module directory
|
# Get the migrations module directory
|
||||||
module_name = self.migrations_module(app_config.label)
|
module_name = self.migrations_module(app_config.label)
|
||||||
was_loaded = module_name in sys.modules
|
was_loaded = module_name in sys.modules
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class NoModelTests(TestCase):
|
|
||||||
""" A placeholder test case. See empty.tests for more info. """
|
|
||||||
pass
|
|
|
@ -1,7 +1,4 @@
|
||||||
from django.apps import app_cache
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.utils import override_settings
|
|
||||||
from django.utils import six
|
|
||||||
|
|
||||||
from .models import Empty
|
from .models import Empty
|
||||||
|
|
||||||
|
@ -16,20 +13,3 @@ class EmptyModelTests(TestCase):
|
||||||
self.assertTrue(m.id is not None)
|
self.assertTrue(m.id is not None)
|
||||||
existing = Empty(m.id)
|
existing = Empty(m.id)
|
||||||
existing.save()
|
existing.save()
|
||||||
|
|
||||||
|
|
||||||
class NoModelTests(TestCase):
|
|
||||||
"""
|
|
||||||
Test for #7198 to ensure that the proper error message is raised
|
|
||||||
when attempting to load an app with no models.py file.
|
|
||||||
|
|
||||||
Because the test runner won't currently load a test module with no
|
|
||||||
models.py file, this TestCase instead lives in this module.
|
|
||||||
|
|
||||||
It seemed like an appropriate home for it.
|
|
||||||
"""
|
|
||||||
@override_settings(INSTALLED_APPS=("empty.no_models",))
|
|
||||||
def test_no_models(self):
|
|
||||||
with six.assertRaisesRegex(self, LookupError,
|
|
||||||
"No app with label 'no_models'."):
|
|
||||||
app_cache.get_app_config('no_models')
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
from django.apps import app_cache
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class NoModelTests(TestCase):
|
||||||
|
|
||||||
|
def test_no_models(self):
|
||||||
|
"""Test that it's possible to load an app with no models.py file."""
|
||||||
|
app_config = app_cache.get_app_config('no_models')
|
||||||
|
self.assertIsNone(app_config.models_module)
|
Loading…
Reference in New Issue