|
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 |