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