~ubuntu-branches/debian/sid/python-django/sid

« back to all changes in this revision

Viewing changes to django/apps/registry.py

  • Committer: Package Import Robot
  • Author(s): Raphaël Hertzog
  • Date: 2014-09-17 14:15:11 UTC
  • mfrom: (1.3.17) (6.2.18 experimental)
  • Revision ID: package-import@ubuntu.com-20140917141511-icneokthe9ww5sk4
Tags: 1.7-2
* Release to unstable.
* Add a migrate-south sample script to help users apply their South
  migrations. Thanks to Brian May.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from collections import Counter, defaultdict, OrderedDict
 
2
import os
 
3
import sys
 
4
import threading
 
5
import warnings
 
6
 
 
7
from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
 
8
from django.utils import lru_cache
 
9
from django.utils.deprecation import RemovedInDjango19Warning
 
10
from django.utils._os import upath
 
11
 
 
12
from .config import AppConfig
 
13
 
 
14
 
 
15
class Apps(object):
 
16
    """
 
17
    A registry that stores the configuration of installed applications.
 
18
 
 
19
    It also keeps track of models eg. to provide reverse-relations.
 
20
    """
 
21
 
 
22
    def __init__(self, installed_apps=()):
 
23
        # installed_apps is set to None when creating the master registry
 
24
        # because it cannot be populated at that point. Other registries must
 
25
        # provide a list of installed apps and are populated immediately.
 
26
        if installed_apps is None and hasattr(sys.modules[__name__], 'apps'):
 
27
            raise RuntimeError("You must supply an installed_apps argument.")
 
28
 
 
29
        # Mapping of app labels => model names => model classes. Every time a
 
30
        # model is imported, ModelBase.__new__ calls apps.register_model which
 
31
        # creates an entry in all_models. All imported models are registered,
 
32
        # regardless of whether they're defined in an installed application
 
33
        # and whether the registry has been populated. Since it isn't possible
 
34
        # to reimport a module safely (it could reexecute initialization code)
 
35
        # all_models is never overridden or reset.
 
36
        self.all_models = defaultdict(OrderedDict)
 
37
 
 
38
        # Mapping of labels to AppConfig instances for installed apps.
 
39
        self.app_configs = OrderedDict()
 
40
 
 
41
        # Stack of app_configs. Used to store the current state in
 
42
        # set_available_apps and set_installed_apps.
 
43
        self.stored_app_configs = []
 
44
 
 
45
        # Whether the registry is populated.
 
46
        self.apps_ready = self.models_ready = self.ready = False
 
47
 
 
48
        # Lock for thread-safe population.
 
49
        self._lock = threading.Lock()
 
50
 
 
51
        # Pending lookups for lazy relations.
 
52
        self._pending_lookups = {}
 
53
 
 
54
        # Populate apps and models, unless it's the master registry.
 
55
        if installed_apps is not None:
 
56
            self.populate(installed_apps)
 
57
 
 
58
    def populate(self, installed_apps=None):
 
59
        """
 
60
        Loads application configurations and models.
 
61
 
 
62
        This method imports each application module and then each model module.
 
63
 
 
64
        It is thread safe and idempotent, but not reentrant.
 
65
        """
 
66
        if self.ready:
 
67
            return
 
68
 
 
69
        # populate() might be called by two threads in parallel on servers
 
70
        # that create threads before initializing the WSGI callable.
 
71
        with self._lock:
 
72
            if self.ready:
 
73
                return
 
74
 
 
75
            # app_config should be pristine, otherwise the code below won't
 
76
            # guarantee that the order matches the order in INSTALLED_APPS.
 
77
            if self.app_configs:
 
78
                raise RuntimeError("populate() isn't reentrant")
 
79
 
 
80
            # Load app configs and app modules.
 
81
            for entry in installed_apps:
 
82
                if isinstance(entry, AppConfig):
 
83
                    app_config = entry
 
84
                else:
 
85
                    app_config = AppConfig.create(entry)
 
86
                if app_config.label in self.app_configs:
 
87
                    raise ImproperlyConfigured(
 
88
                        "Application labels aren't unique, "
 
89
                        "duplicates: %s" % app_config.label)
 
90
 
 
91
                self.app_configs[app_config.label] = app_config
 
92
 
 
93
            # Check for duplicate app names.
 
94
            counts = Counter(
 
95
                app_config.name for app_config in self.app_configs.values())
 
96
            duplicates = [
 
97
                name for name, count in counts.most_common() if count > 1]
 
98
            if duplicates:
 
99
                raise ImproperlyConfigured(
 
100
                    "Application names aren't unique, "
 
101
                    "duplicates: %s" % ", ".join(duplicates))
 
102
 
 
103
            self.apps_ready = True
 
104
 
 
105
            # Load models.
 
106
            for app_config in self.app_configs.values():
 
107
                all_models = self.all_models[app_config.label]
 
108
                app_config.import_models(all_models)
 
109
 
 
110
            self.clear_cache()
 
111
 
 
112
            self.models_ready = True
 
113
 
 
114
            for app_config in self.get_app_configs():
 
115
                app_config.ready()
 
116
 
 
117
            self.ready = True
 
118
 
 
119
    def check_apps_ready(self):
 
120
        """
 
121
        Raises an exception if all apps haven't been imported yet.
 
122
        """
 
123
        if not self.apps_ready:
 
124
            raise AppRegistryNotReady("Apps aren't loaded yet.")
 
125
 
 
126
    def check_models_ready(self):
 
127
        """
 
128
        Raises an exception if all models haven't been imported yet.
 
129
        """
 
130
        if not self.models_ready:
 
131
            raise AppRegistryNotReady("Models aren't loaded yet.")
 
132
 
 
133
    def get_app_configs(self):
 
134
        """
 
135
        Imports applications and returns an iterable of app configs.
 
136
        """
 
137
        self.check_apps_ready()
 
138
        return self.app_configs.values()
 
139
 
 
140
    def get_app_config(self, app_label):
 
141
        """
 
142
        Imports applications and returns an app config for the given label.
 
143
 
 
144
        Raises LookupError if no application exists with this label.
 
145
        """
 
146
        self.check_apps_ready()
 
147
        try:
 
148
            return self.app_configs[app_label]
 
149
        except KeyError:
 
150
            raise LookupError("No installed app with label '%s'." % app_label)
 
151
 
 
152
    # This method is performance-critical at least for Django's test suite.
 
153
    @lru_cache.lru_cache(maxsize=None)
 
154
    def get_models(self, app_mod=None, include_auto_created=False,
 
155
                   include_deferred=False, include_swapped=False):
 
156
        """
 
157
        Returns a list of all installed models.
 
158
 
 
159
        By default, the following models aren't included:
 
160
 
 
161
        - auto-created models for many-to-many relations without
 
162
          an explicit intermediate table,
 
163
        - models created to satisfy deferred attribute queries,
 
164
        - models that have been swapped out.
 
165
 
 
166
        Set the corresponding keyword argument to True to include such models.
 
167
        """
 
168
        self.check_models_ready()
 
169
        if app_mod:
 
170
            warnings.warn(
 
171
                "The app_mod argument of get_models is deprecated.",
 
172
                RemovedInDjango19Warning, stacklevel=2)
 
173
            app_label = app_mod.__name__.split('.')[-2]
 
174
            try:
 
175
                return list(self.get_app_config(app_label).get_models(
 
176
                    include_auto_created, include_deferred, include_swapped))
 
177
            except LookupError:
 
178
                return []
 
179
 
 
180
        result = []
 
181
        for app_config in self.app_configs.values():
 
182
            result.extend(list(app_config.get_models(
 
183
                include_auto_created, include_deferred, include_swapped)))
 
184
        return result
 
185
 
 
186
    def get_model(self, app_label, model_name=None):
 
187
        """
 
188
        Returns the model matching the given app_label and model_name.
 
189
 
 
190
        As a shortcut, this function also accepts a single argument in the
 
191
        form <app_label>.<model_name>.
 
192
 
 
193
        model_name is case-insensitive.
 
194
 
 
195
        Raises LookupError if no application exists with this label, or no
 
196
        model exists with this name in the application. Raises ValueError if
 
197
        called with a single argument that doesn't contain exactly one dot.
 
198
        """
 
199
        self.check_models_ready()
 
200
        if model_name is None:
 
201
            app_label, model_name = app_label.split('.')
 
202
        return self.get_app_config(app_label).get_model(model_name.lower())
 
203
 
 
204
    def register_model(self, app_label, model):
 
205
        # Since this method is called when models are imported, it cannot
 
206
        # perform imports because of the risk of import loops. It mustn't
 
207
        # call get_app_config().
 
208
        model_name = model._meta.model_name
 
209
        app_models = self.all_models[app_label]
 
210
        if model_name in app_models:
 
211
            raise RuntimeError(
 
212
                "Conflicting '%s' models in application '%s': %s and %s." %
 
213
                (model_name, app_label, app_models[model_name], model))
 
214
        app_models[model_name] = model
 
215
        self.clear_cache()
 
216
 
 
217
    def is_installed(self, app_name):
 
218
        """
 
219
        Checks whether an application with this name exists in the registry.
 
220
 
 
221
        app_name is the full name of the app eg. 'django.contrib.admin'.
 
222
        """
 
223
        self.check_apps_ready()
 
224
        return any(ac.name == app_name for ac in self.app_configs.values())
 
225
 
 
226
    def get_containing_app_config(self, object_name):
 
227
        """
 
228
        Look for an app config containing a given object.
 
229
 
 
230
        object_name is the dotted Python path to the object.
 
231
 
 
232
        Returns the app config for the inner application in case of nesting.
 
233
        Returns None if the object isn't in any registered app config.
 
234
        """
 
235
        # In Django 1.7 and 1.8, it's allowed to call this method at import
 
236
        # time, even while the registry is being populated. In Django 1.9 and
 
237
        # later, that should be forbidden with `self.check_apps_ready()`.
 
238
        candidates = []
 
239
        for app_config in self.app_configs.values():
 
240
            if object_name.startswith(app_config.name):
 
241
                subpath = object_name[len(app_config.name):]
 
242
                if subpath == '' or subpath[0] == '.':
 
243
                    candidates.append(app_config)
 
244
        if candidates:
 
245
            return sorted(candidates, key=lambda ac: -len(ac.name))[0]
 
246
 
 
247
    def get_registered_model(self, app_label, model_name):
 
248
        """
 
249
        Similar to get_model(), but doesn't require that an app exists with
 
250
        the given app_label.
 
251
 
 
252
        It's safe to call this method at import time, even while the registry
 
253
        is being populated.
 
254
        """
 
255
        model = self.all_models[app_label].get(model_name.lower())
 
256
        if model is None:
 
257
            raise LookupError(
 
258
                "Model '%s.%s' not registered." % (app_label, model_name))
 
259
        return model
 
260
 
 
261
    def set_available_apps(self, available):
 
262
        """
 
263
        Restricts the set of installed apps used by get_app_config[s].
 
264
 
 
265
        available must be an iterable of application names.
 
266
 
 
267
        set_available_apps() must be balanced with unset_available_apps().
 
268
 
 
269
        Primarily used for performance optimization in TransactionTestCase.
 
270
 
 
271
        This method is safe is the sense that it doesn't trigger any imports.
 
272
        """
 
273
        available = set(available)
 
274
        installed = set(app_config.name for app_config in self.get_app_configs())
 
275
        if not available.issubset(installed):
 
276
            raise ValueError("Available apps isn't a subset of installed "
 
277
                "apps, extra apps: %s" % ", ".join(available - installed))
 
278
 
 
279
        self.stored_app_configs.append(self.app_configs)
 
280
        self.app_configs = OrderedDict(
 
281
            (label, app_config)
 
282
            for label, app_config in self.app_configs.items()
 
283
            if app_config.name in available)
 
284
        self.clear_cache()
 
285
 
 
286
    def unset_available_apps(self):
 
287
        """
 
288
        Cancels a previous call to set_available_apps().
 
289
        """
 
290
        self.app_configs = self.stored_app_configs.pop()
 
291
        self.clear_cache()
 
292
 
 
293
    def set_installed_apps(self, installed):
 
294
        """
 
295
        Enables a different set of installed apps for get_app_config[s].
 
296
 
 
297
        installed must be an iterable in the same format as INSTALLED_APPS.
 
298
 
 
299
        set_installed_apps() must be balanced with unset_installed_apps(),
 
300
        even if it exits with an exception.
 
301
 
 
302
        Primarily used as a receiver of the setting_changed signal in tests.
 
303
 
 
304
        This method may trigger new imports, which may add new models to the
 
305
        registry of all imported models. They will stay in the registry even
 
306
        after unset_installed_apps(). Since it isn't possible to replay
 
307
        imports safely (eg. that could lead to registering listeners twice),
 
308
        models are registered when they're imported and never removed.
 
309
        """
 
310
        if not self.ready:
 
311
            raise AppRegistryNotReady("App registry isn't ready yet.")
 
312
        self.stored_app_configs.append(self.app_configs)
 
313
        self.app_configs = OrderedDict()
 
314
        self.apps_ready = self.models_ready = self.ready = False
 
315
        self.clear_cache()
 
316
        self.populate(installed)
 
317
 
 
318
    def unset_installed_apps(self):
 
319
        """
 
320
        Cancels a previous call to set_installed_apps().
 
321
        """
 
322
        self.app_configs = self.stored_app_configs.pop()
 
323
        self.apps_ready = self.models_ready = self.ready = True
 
324
        self.clear_cache()
 
325
 
 
326
    def clear_cache(self):
 
327
        """
 
328
        Clears all internal caches, for methods that alter the app registry.
 
329
 
 
330
        This is mostly used in tests.
 
331
        """
 
332
        self.get_models.cache_clear()
 
333
 
 
334
    ### DEPRECATED METHODS GO BELOW THIS LINE ###
 
335
 
 
336
    def load_app(self, app_name):
 
337
        """
 
338
        Loads the app with the provided fully qualified name, and returns the
 
339
        model module.
 
340
        """
 
341
        warnings.warn(
 
342
            "load_app(app_name) is deprecated.",
 
343
            RemovedInDjango19Warning, stacklevel=2)
 
344
        app_config = AppConfig.create(app_name)
 
345
        app_config.import_models(self.all_models[app_config.label])
 
346
        self.app_configs[app_config.label] = app_config
 
347
        self.clear_cache()
 
348
        return app_config.models_module
 
349
 
 
350
    def app_cache_ready(self):
 
351
        warnings.warn(
 
352
            "app_cache_ready() is deprecated in favor of the ready property.",
 
353
            RemovedInDjango19Warning, stacklevel=2)
 
354
        return self.ready
 
355
 
 
356
    def get_app(self, app_label):
 
357
        """
 
358
        Returns the module containing the models for the given app_label.
 
359
        """
 
360
        warnings.warn(
 
361
            "get_app_config(app_label).models_module supersedes get_app(app_label).",
 
362
            RemovedInDjango19Warning, stacklevel=2)
 
363
        try:
 
364
            models_module = self.get_app_config(app_label).models_module
 
365
        except LookupError as exc:
 
366
            # Change the exception type for backwards compatibility.
 
367
            raise ImproperlyConfigured(*exc.args)
 
368
        if models_module is None:
 
369
            raise ImproperlyConfigured(
 
370
                "App '%s' doesn't have a models module." % app_label)
 
371
        return models_module
 
372
 
 
373
    def get_apps(self):
 
374
        """
 
375
        Returns a list of all installed modules that contain models.
 
376
        """
 
377
        warnings.warn(
 
378
            "[a.models_module for a in get_app_configs()] supersedes get_apps().",
 
379
            RemovedInDjango19Warning, stacklevel=2)
 
380
        app_configs = self.get_app_configs()
 
381
        return [app_config.models_module for app_config in app_configs
 
382
                if app_config.models_module is not None]
 
383
 
 
384
    def _get_app_package(self, app):
 
385
        return '.'.join(app.__name__.split('.')[:-1])
 
386
 
 
387
    def get_app_package(self, app_label):
 
388
        warnings.warn(
 
389
            "get_app_config(label).name supersedes get_app_package(label).",
 
390
            RemovedInDjango19Warning, stacklevel=2)
 
391
        return self._get_app_package(self.get_app(app_label))
 
392
 
 
393
    def _get_app_path(self, app):
 
394
        if hasattr(app, '__path__'):        # models/__init__.py package
 
395
            app_path = app.__path__[0]
 
396
        else:                               # models.py module
 
397
            app_path = app.__file__
 
398
        return os.path.dirname(upath(app_path))
 
399
 
 
400
    def get_app_path(self, app_label):
 
401
        warnings.warn(
 
402
            "get_app_config(label).path supersedes get_app_path(label).",
 
403
            RemovedInDjango19Warning, stacklevel=2)
 
404
        return self._get_app_path(self.get_app(app_label))
 
405
 
 
406
    def get_app_paths(self):
 
407
        """
 
408
        Returns a list of paths to all installed apps.
 
409
 
 
410
        Useful for discovering files at conventional locations inside apps
 
411
        (static files, templates, etc.)
 
412
        """
 
413
        warnings.warn(
 
414
            "[a.path for a in get_app_configs()] supersedes get_app_paths().",
 
415
            RemovedInDjango19Warning, stacklevel=2)
 
416
        self.check_apps_ready()
 
417
        app_paths = []
 
418
        for app in self.get_apps():
 
419
            app_paths.append(self._get_app_path(app))
 
420
        return app_paths
 
421
 
 
422
    def register_models(self, app_label, *models):
 
423
        """
 
424
        Register a set of models as belonging to an app.
 
425
        """
 
426
        warnings.warn(
 
427
            "register_models(app_label, *models) is deprecated.",
 
428
            RemovedInDjango19Warning, stacklevel=2)
 
429
        for model in models:
 
430
            self.register_model(app_label, model)
 
431
 
 
432
 
 
433
apps = Apps(installed_apps=None)