Django

Code

root/django/trunk/django/db/models/loading.py

Revision 7721, 6.9 kB (checked in by lukeplant, 5 months ago)

Added tests for corner case with deleting where objects are deleted in the wrong order.

These tests currently fail, by design, fix will be committed shortly.

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