Previously a RuntimeError was raised every time two models clashed
in the app registry. This prevented reloading a module in a REPL;
while it's not recommended to do so, we decided not to forbid this
use-case by turning the error into a warning.
Thanks @dfunckt and Sergey Pashinin for the initial patches.
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.
Also document the conditions under which a namespace package may or may not be
a Django app, and raise a clearer error message in those cases where it may not
be.
Thanks Aymeric for review and consultation.
Since the app registry is always populated before the first request is
processed, the situation described in #18251 for the old app cache
cannot happen any more.
Refs #18251, #21628.
Now that the refactorings are complete, it isn't particularly useful any
more, nor very well named. Let's keep the API as simple as possible.
Fixed#21689.
To the best of my understanding, since populate_models() is now called
as soon as Django starts, it cannot be called while a models module is
being imported, and that removes the need for postponing.
(If hell breaks loose we'll revert this commit.)
Refs #21681.
Since it triggers imports, it shouldn't be done lightly.
This commit adds a public API for doing it explicitly, django.setup(),
and does it automatically when using manage.py and wsgi.py.
Returning None on errors required unpythonic error checking and was
inconsistent with get_app_config.
get_model was a private API until the previous commit, but given that it
was certainly used in third party software, the change is explained in
the release notes.
Applied the same change to get_registered_model, which is a new private
API introduced during the recent refactoring.
This removes the gap between the master app registry and ad-hoc app
registries created by the migration framework, specifically in terms
of behavior of the get_model[s] methods.
This commit contains a stealth feature that I'd rather not describe.
register_model is called exactly once in the entire Django code base, at the
bottom of ModelBase.__new__:
new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
ModelBase.__new__ exits prematurely 120 lines earlier (sigh) if a model with
the same name is already registered:
if new_class._meta.apps.get_registered_model(new_class._meta.app_label, name):
return
(This isn't the exact code, but it's equivalent.)
apps.register_model and apps.get_registered_model are essentially a setter and
a getter for apps.all_models, and apps.register_model is the only setter. As a
consequence, new_class._meta.apps.all_models cannot change in-between.
Considering that name == new_class.__name__, we can conclude that
register_model(app_label, model) is always called with such arguments that
get_registered_model(app_label, model.__name__) returns None.
Considering that model._meta.model_name == model.__name__.lower(), and looking
at the implementation of register_model and get_registered_model, this proves
that self.all_models[app_label] doesn't contain model._meta.model_name in
register_model, allowing us to simplify the implementation.
It raises ImportError whenever an entry in INSTALLED_APPS points
(directly or indirectly via AppConfig.name) to a non-existing module
and ImproperlyConfigured in all other cases.
Catching ImportError and re-raising ImproperlyConfigured tends to make
circular imports more difficult to diagnose.