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