web/lib/django/db/models/loading.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 "Utilities for loading models and the modules that contain them."
       
     2 
       
     3 from django.conf import settings
       
     4 from django.core.exceptions import ImproperlyConfigured
       
     5 from django.utils.datastructures import SortedDict
       
     6 from django.utils.importlib import import_module
       
     7 from django.utils.module_loading import module_has_submodule
       
     8 
       
     9 import imp
       
    10 import sys
       
    11 import os
       
    12 import threading
       
    13 
       
    14 __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
       
    15         'load_app', 'app_cache_ready')
       
    16 
       
    17 class AppCache(object):
       
    18     """
       
    19     A cache that stores installed applications and their models. Used to
       
    20     provide reverse-relations and for app introspection (e.g. admin).
       
    21     """
       
    22     # Use the Borg pattern to share state between all instances. Details at
       
    23     # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531.
       
    24     __shared_state = dict(
       
    25         # Keys of app_store are the model modules for each application.
       
    26         app_store = SortedDict(),
       
    27 
       
    28         # Mapping of app_labels to a dictionary of model names to model code.
       
    29         app_models = SortedDict(),
       
    30 
       
    31         # Mapping of app_labels to errors raised when trying to import the app.
       
    32         app_errors = {},
       
    33 
       
    34         # -- Everything below here is only used when populating the cache --
       
    35         loaded = False,
       
    36         handled = {},
       
    37         postponed = [],
       
    38         nesting_level = 0,
       
    39         write_lock = threading.RLock(),
       
    40         _get_models_cache = {},
       
    41     )
       
    42 
       
    43     def __init__(self):
       
    44         self.__dict__ = self.__shared_state
       
    45 
       
    46     def _populate(self):
       
    47         """
       
    48         Fill in all the cache information. This method is threadsafe, in the
       
    49         sense that every caller will see the same state upon return, and if the
       
    50         cache is already initialised, it does no work.
       
    51         """
       
    52         if self.loaded:
       
    53             return
       
    54         self.write_lock.acquire()
       
    55         try:
       
    56             if self.loaded:
       
    57                 return
       
    58             for app_name in settings.INSTALLED_APPS:
       
    59                 if app_name in self.handled:
       
    60                     continue
       
    61                 self.load_app(app_name, True)
       
    62             if not self.nesting_level:
       
    63                 for app_name in self.postponed:
       
    64                     self.load_app(app_name)
       
    65                 self.loaded = True
       
    66         finally:
       
    67             self.write_lock.release()
       
    68 
       
    69     def load_app(self, app_name, can_postpone=False):
       
    70         """
       
    71         Loads the app with the provided fully qualified name, and returns the
       
    72         model module.
       
    73         """
       
    74         self.handled[app_name] = None
       
    75         self.nesting_level += 1
       
    76         app_module = import_module(app_name)
       
    77         try:
       
    78             models = import_module('.models', app_name)
       
    79         except ImportError:
       
    80             self.nesting_level -= 1
       
    81             # If the app doesn't have a models module, we can just ignore the
       
    82             # ImportError and return no models for it.
       
    83             if not module_has_submodule(app_module, 'models'):
       
    84                 return None
       
    85             # But if the app does have a models module, we need to figure out
       
    86             # whether to suppress or propagate the error. If can_postpone is
       
    87             # True then it may be that the package is still being imported by
       
    88             # Python and the models module isn't available yet. So we add the
       
    89             # app to the postponed list and we'll try it again after all the
       
    90             # recursion has finished (in populate). If can_postpone is False
       
    91             # then it's time to raise the ImportError.
       
    92             else:
       
    93                 if can_postpone:
       
    94                     self.postponed.append(app_name)
       
    95                     return None
       
    96                 else:
       
    97                     raise
       
    98 
       
    99         self.nesting_level -= 1
       
   100         if models not in self.app_store:
       
   101             self.app_store[models] = len(self.app_store)
       
   102         return models
       
   103 
       
   104     def app_cache_ready(self):
       
   105         """
       
   106         Returns true if the model cache is fully populated.
       
   107 
       
   108         Useful for code that wants to cache the results of get_models() for
       
   109         themselves once it is safe to do so.
       
   110         """
       
   111         return self.loaded
       
   112 
       
   113     def get_apps(self):
       
   114         "Returns a list of all installed modules that contain models."
       
   115         self._populate()
       
   116 
       
   117         # Ensure the returned list is always in the same order (with new apps
       
   118         # added at the end). This avoids unstable ordering on the admin app
       
   119         # list page, for example.
       
   120         apps = [(v, k) for k, v in self.app_store.items()]
       
   121         apps.sort()
       
   122         return [elt[1] for elt in apps]
       
   123 
       
   124     def get_app(self, app_label, emptyOK=False):
       
   125         """
       
   126         Returns the module containing the models for the given app_label. If
       
   127         the app has no models in it and 'emptyOK' is True, returns None.
       
   128         """
       
   129         self._populate()
       
   130         self.write_lock.acquire()
       
   131         try:
       
   132             for app_name in settings.INSTALLED_APPS:
       
   133                 if app_label == app_name.split('.')[-1]:
       
   134                     mod = self.load_app(app_name, False)
       
   135                     if mod is None:
       
   136                         if emptyOK:
       
   137                             return None
       
   138                     else:
       
   139                         return mod
       
   140             raise ImproperlyConfigured("App with label %s could not be found" % app_label)
       
   141         finally:
       
   142             self.write_lock.release()
       
   143 
       
   144     def get_app_errors(self):
       
   145         "Returns the map of known problems with the INSTALLED_APPS."
       
   146         self._populate()
       
   147         return self.app_errors
       
   148 
       
   149     def get_models(self, app_mod=None, include_auto_created=False, include_deferred=False):
       
   150         """
       
   151         Given a module containing models, returns a list of the models.
       
   152         Otherwise returns a list of all installed models.
       
   153 
       
   154         By default, auto-created models (i.e., m2m models without an
       
   155         explicit intermediate table) are not included. However, if you
       
   156         specify include_auto_created=True, they will be.
       
   157 
       
   158         By default, models created to satisfy deferred attribute
       
   159         queries are *not* included in the list of models. However, if
       
   160         you specify include_deferred, they will be.
       
   161         """
       
   162         cache_key = (app_mod, include_auto_created, include_deferred)
       
   163         try:
       
   164             return self._get_models_cache[cache_key]
       
   165         except KeyError:
       
   166             pass
       
   167         self._populate()
       
   168         if app_mod:
       
   169             app_list = [self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict())]
       
   170         else:
       
   171             app_list = self.app_models.itervalues()
       
   172         model_list = []
       
   173         for app in app_list:
       
   174             model_list.extend(
       
   175                 model for model in app.values()
       
   176                 if ((not model._deferred or include_deferred)
       
   177                     and (not model._meta.auto_created or include_auto_created))
       
   178             )
       
   179         self._get_models_cache[cache_key] = model_list
       
   180         return model_list
       
   181 
       
   182     def get_model(self, app_label, model_name, seed_cache=True):
       
   183         """
       
   184         Returns the model matching the given app_label and case-insensitive
       
   185         model_name.
       
   186 
       
   187         Returns None if no model is found.
       
   188         """
       
   189         if seed_cache:
       
   190             self._populate()
       
   191         return self.app_models.get(app_label, SortedDict()).get(model_name.lower())
       
   192 
       
   193     def register_models(self, app_label, *models):
       
   194         """
       
   195         Register a set of models as belonging to an app.
       
   196         """
       
   197         for model in models:
       
   198             # Store as 'name: model' pair in a dictionary
       
   199             # in the app_models dictionary
       
   200             model_name = model._meta.object_name.lower()
       
   201             model_dict = self.app_models.setdefault(app_label, SortedDict())
       
   202             if model_name in model_dict:
       
   203                 # The same model may be imported via different paths (e.g.
       
   204                 # appname.models and project.appname.models). We use the source
       
   205                 # filename as a means to detect identity.
       
   206                 fname1 = os.path.abspath(sys.modules[model.__module__].__file__)
       
   207                 fname2 = os.path.abspath(sys.modules[model_dict[model_name].__module__].__file__)
       
   208                 # Since the filename extension could be .py the first time and
       
   209                 # .pyc or .pyo the second time, ignore the extension when
       
   210                 # comparing.
       
   211                 if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
       
   212                     continue
       
   213             model_dict[model_name] = model
       
   214         self._get_models_cache.clear()
       
   215 
       
   216 cache = AppCache()
       
   217 
       
   218 # These methods were always module level, so are kept that way for backwards
       
   219 # compatibility.
       
   220 get_apps = cache.get_apps
       
   221 get_app = cache.get_app
       
   222 get_app_errors = cache.get_app_errors
       
   223 get_models = cache.get_models
       
   224 get_model = cache.get_model
       
   225 register_models = cache.register_models
       
   226 load_app = cache.load_app
       
   227 app_cache_ready = cache.app_cache_ready