Code

Ticket #3591: app_labels.4.diff

File app_labels.4.diff, 29.4 KB (added by Vinay Sajip <vinay_sajip@…>, 7 years ago)

Updated patch to work cleanly against trunk revision r5171.

Line 
1=== modified file 'django/bin/make-messages.py'
2--- django/bin/make-messages.py
3+++ django/bin/make-messages.py
4@@ -129,6 +129,22 @@
5                 print "errors happened while running msguniq"
6                 print errors
7                 sys.exit(8)
8+            # Try importing the local settings. This will work if you're
9+            # in the project directory
10+            sys.path.insert(0, '.')
11+            try:
12+                import settings
13+                apps = settings.INSTALLED_APPS
14+            except (ImportError, AttributeError):
15+                apps = []
16+            del sys.path[0]
17+            # Now look for all applications which have a verbose name
18+            # which is different from the default. If different, add
19+            # an internationalization string.
20+            for app in apps:
21+                if (not isinstance(app, basestring)) and (app.verbose_name != app.path.split('.')[-1]):
22+                    s = '\nmsgid "%s"\nmsgstr ""\n' % app.verbose_name
23+                    msgs += s
24             open(potfile, 'w').write(msgs)
25             if os.path.exists(pofile):
26                 (stdin, stdout, stderr) = os.popen3('msgmerge -q "%s" "%s"' % (pofile, potfile), 'b')
27
28=== modified file 'django/conf/__init__.py'
29--- django/conf/__init__.py     
30+++ django/conf/__init__.py     
31@@ -97,13 +97,13 @@
32         # of all those apps.
33         new_installed_apps = []
34         for app in self.INSTALLED_APPS:
35-            if app.endswith('.*'):
36+            if not isinstance(app, basestring) or not app.endswith('.*'):
37+                new_installed_apps.append(app)
38+            else:
39                 appdir = os.path.dirname(__import__(app[:-2], {}, {}, ['']).__file__)
40                 for d in os.listdir(appdir):
41                     if d.isalpha() and os.path.isdir(os.path.join(appdir, d)):
42                         new_installed_apps.append('%s.%s' % (app[:-2], d))
43-            else:
44-                new_installed_apps.append(app)
45         self.INSTALLED_APPS = new_installed_apps
46 
47         if hasattr(time, 'tzset'):
48@@ -147,3 +147,26 @@
49     return gettext(*args)
50 
51 __builtins__['_'] = first_time_gettext
52+
53+class app(object):
54+    """Configuration directive for specifying an app."""
55+    def __init__(self, path, app_label=None, verbose_name=None):
56+        self.path = path
57+        # if name isn't specified, get the last part of the Python dotted path
58+        self.label = app_label or path.split('.')[-1]
59+        self.verbose_name = verbose_name or self.label
60+        self.app_module = None    # will be filled in by loading.py
61+        self.models_module = None # will be filled in by loading.py
62+
63+    def __repr__(self):
64+        return "<app: %s>" % self.path
65+
66+def get_installed_app_paths():
67+    "Return the paths of all entries in settings.INSTALLED_APPS."
68+    rv = []
69+    for a in settings.INSTALLED_APPS:
70+        if isinstance(a, basestring):
71+            rv.append(a)
72+        else:
73+            rv.append(a.path)
74+    return rv
75
76=== modified file 'django/contrib/admin/templatetags/adminapplist.py'
77--- django/contrib/admin/templatetags/adminapplist.py   
78+++ django/contrib/admin/templatetags/adminapplist.py   
79@@ -18,7 +18,7 @@
80             app_models = get_models(app)
81             if not app_models:
82                 continue
83-            app_label = app_models[0]._meta.app_label
84+            app_label = app.label
85 
86             has_module_perms = user.has_module_perms(app_label)
87 
88@@ -49,7 +49,7 @@
89                     model_list = [x for key, x in decorated]
90 
91                     app_list.append({
92-                        'name': app_label.title(),
93+                        'name': _(app.verbose_name).title(),
94                         'has_module_perms': has_module_perms,
95                         'models': model_list,
96                     })
97
98=== modified file 'django/contrib/admin/views/doc.py'
99--- django/contrib/admin/views/doc.py   
100+++ django/contrib/admin/views/doc.py   
101@@ -159,11 +159,11 @@
102 
103     # Get the model class.
104     try:
105-        app_mod = models.get_app(app_label)
106+        app = models.get_app(app_label)
107     except ImproperlyConfigured:
108         raise Http404, _("App %r not found") % app_label
109     model = None
110-    for m in models.get_models(app_mod):
111+    for m in models.get_models(app):
112         if m._meta.object_name.lower() == model_name:
113             model = m
114             break
115
116=== modified file 'django/contrib/auth/management.py'
117--- django/contrib/auth/management.py   
118+++ django/contrib/auth/management.py   
119@@ -3,8 +3,7 @@
120 """
121 
122 from django.dispatch import dispatcher
123-from django.db.models import get_models, signals
124-from django.contrib.auth import models as auth_app
125+from django.db.models import get_models, signals, find_app
126 
127 def _get_permission_codename(action, opts):
128     return '%s_%s' % (action, opts.object_name.lower())
129@@ -46,4 +45,4 @@
130             break
131 
132 dispatcher.connect(create_permissions, signal=signals.post_syncdb)
133-dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)
134+dispatcher.connect(create_superuser, sender=find_app('django.contrib.auth'), signal=signals.post_syncdb)
135
136=== modified file 'django/contrib/sites/management.py'
137--- django/contrib/sites/management.py 
138+++ django/contrib/sites/management.py 
139@@ -3,9 +3,8 @@
140 """
141 
142 from django.dispatch import dispatcher
143-from django.db.models import signals
144+from django.db.models import signals, find_app
145 from django.contrib.sites.models import Site
146-from django.contrib.sites import models as site_app
147 
148 def create_default_site(app, created_models, verbosity):
149     if Site in created_models:
150@@ -14,4 +13,4 @@
151         s = Site(domain="example.com", name="example.com")
152         s.save()
153 
154-dispatcher.connect(create_default_site, sender=site_app, signal=signals.post_syncdb)
155+dispatcher.connect(create_default_site, sender=find_app('django.contrib.sites'), signal=signals.post_syncdb)
156
157=== modified file 'django/core/management.py'
158--- django/core/management.py   
159+++ django/core/management.py   
160@@ -368,7 +368,7 @@
161     from django.conf import settings
162 
163     opts = model._meta
164-    app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
165+    app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).models_module.__file__), 'sql'))
166     output = []
167 
168     # Some backends can't execute more than one SQL statement at a time,
169@@ -396,7 +396,7 @@
170     output = []
171 
172     app_models = get_models(app)
173-    app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql'))
174+    app_dir = os.path.normpath(os.path.join(os.path.dirname(app.models_module.__file__), 'sql'))
175 
176     for model in app_models:
177         output.extend(get_custom_sql_for_model(model))
178@@ -456,7 +456,7 @@
179     from django.dispatch import dispatcher
180     # Emit the post_sync signal for every application.
181     for app in models.get_apps():
182-        app_name = app.__name__.split('.')[-2]
183+        app_name = app.label
184         if verbosity >= 2:
185             print "Running post-sync handlers for application", app_name
186         dispatcher.send(signal=models.signals.post_syncdb, sender=app,
187@@ -466,7 +466,7 @@
188 def syncdb(verbosity=1, interactive=True):
189     "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
190     from django.db import connection, transaction, models, get_creation_module
191-    from django.conf import settings
192+    from django.conf import settings, get_installed_app_paths
193 
194     disable_termcolors()
195 
196@@ -475,7 +475,7 @@
197 
198     # Import the 'management' module within each installed app, to register
199     # dispatcher events.
200-    for app_name in settings.INSTALLED_APPS:
201+    for app_name in get_installed_app_paths():
202         try:
203             __import__(app_name + '.management', {}, {}, [''])
204         except ImportError:
205@@ -496,7 +496,7 @@
206 
207     # Create the tables for each model
208     for app in models.get_apps():
209-        app_name = app.__name__.split('.')[-2]
210+        app_name = app.label
211         model_list = models.get_models(app)
212         for model in model_list:
213             # Create the model's database table, if it doesn't already exist.
214@@ -519,7 +519,7 @@
215     # Create the m2m tables. This must be done after all tables have been created
216     # to ensure that all referred tables will exist.
217     for app in models.get_apps():
218-        app_name = app.__name__.split('.')[-2]
219+        app_name = app.label
220         model_list = models.get_models(app)
221         for model in model_list:
222             if model in created_models:
223@@ -539,7 +539,7 @@
224     # Install custom SQL for the app (but only if this
225     # is a model we've just created)
226     for app in models.get_apps():
227-        app_name = app.__name__.split('.')[-2]
228+        app_name = app.label
229         for model in models.get_models(app):
230             if model in created_models:
231                 custom_sql = get_custom_sql_for_model(model)
232@@ -556,9 +556,9 @@
233                     else:
234                         transaction.commit_unless_managed()
235 
236-    # Install SQL indicies for all newly created models
237+    # Install SQL indices for all newly created models
238     for app in models.get_apps():
239-        app_name = app.__name__.split('.')[-2]
240+        app_name = app.label
241         for model in models.get_models(app):
242             if model in created_models:
243                 index_sql = get_sql_indexes_for_model(model)
244@@ -635,7 +635,7 @@
245     "Executes the equivalent of 'get_sql_reset' in the current database."
246     from django.db import connection, transaction
247     from django.conf import settings
248-    app_name = app.__name__.split('.')[-2]
249+    app_name = app.label
250 
251     disable_termcolors()
252 
253@@ -676,7 +676,7 @@
254 
255 def flush(verbosity=1, interactive=True):
256     "Returns all tables in the database to the same state they were in immediately after syncdb."
257-    from django.conf import settings
258+    from django.conf import settings, get_installed_app_paths
259     from django.db import connection, transaction, models
260     from django.dispatch import dispatcher
261 
262@@ -687,7 +687,7 @@
263 
264     # Import the 'management' module within each installed app, to register
265     # dispatcher events.
266-    for app_name in settings.INSTALLED_APPS:
267+    for app_name in get_installed_app_paths():
268         try:
269             __import__(app_name + '.management', {}, {}, [''])
270         except ImportError:
271@@ -1293,9 +1293,9 @@
272     from django.db.models import get_app, get_apps
273 
274     if len(app_labels) == 0:
275-        app_list = get_apps()
276+        app_list = [app.models_module for app in get_apps()]
277     else:
278-        app_list = [get_app(app_label) for app_label in app_labels]
279+        app_list = [get_app(app_label).models_module for app_label in app_labels]
280 
281     test_path = settings.TEST_RUNNER.split('.')
282     # Allow for Python 2.5 relative paths
283@@ -1340,7 +1340,11 @@
284     transaction.enter_transaction_management()
285     transaction.managed(True)
286 
287-    app_fixtures = [os.path.join(os.path.dirname(app.__file__),'fixtures') for app in get_apps()]
288+    app_fixtures = []
289+    for app in get_apps():
290+        if app.models_module:
291+            app_fixtures.append(os.path.join(os.path.dirname(
292+                                app.models_module.__file__),'fixtures'))
293     for fixture_label in fixture_labels:
294         parts = fixture_label.split('.')
295         if len(parts) == 1:
296@@ -1619,19 +1623,19 @@
297         from django.db import models
298         validate(silent_success=True)
299         try:
300-            mod_list = [models.get_app(app_label) for app_label in args[1:]]
301+            app_list = [models.get_app(app_label) for app_label in args[1:]]
302         except ImportError, e:
303             sys.stderr.write(style.ERROR("Error: %s. Are you sure your INSTALLED_APPS setting is correct?\n" % e))
304             sys.exit(1)
305-        if not mod_list:
306+        if not app_list:
307             parser.print_usage_and_exit()
308         if action not in NO_SQL_TRANSACTION:
309             print style.SQL_KEYWORD("BEGIN;")
310-        for mod in mod_list:
311+        for app in app_list:
312             if action == 'reset':
313-                output = action_mapping[action](mod, options.interactive)
314+                output = action_mapping[action](app, options.interactive)
315             else:
316-                output = action_mapping[action](mod)
317+                output = action_mapping[action](app)
318             if output:
319                 print '\n'.join(output)
320         if action not in NO_SQL_TRANSACTION:
321
322=== modified file 'django/db/models/__init__.py'
323--- django/db/models/__init__.py       
324+++ django/db/models/__init__.py       
325@@ -2,7 +2,7 @@
326 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
327 from django.core import validators
328 from django.db import backend, connection
329-from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
330+from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models, find_app
331 from django.db.models.query import Q
332 from django.db.models.manager import Manager
333 from django.db.models.base import Model, AdminOptions
334
335=== modified file 'django/db/models/base.py'
336--- django/db/models/base.py   
337+++ django/db/models/base.py   
338@@ -8,7 +8,7 @@
339 from django.db.models.options import Options, AdminOptions
340 from django.db import connection, backend, transaction
341 from django.db.models import signals
342-from django.db.models.loading import register_models, get_model
343+from django.db.models.loading import register_models, get_model, get_app_label
344 from django.dispatch import dispatcher
345 from django.utils.datastructures import SortedDict
346 from django.utils.functional import curry
347@@ -42,12 +42,9 @@
348                 new_class._meta.parents.append(base)
349                 new_class._meta.parents.extend(base._meta.parents)
350 
351-
352         if getattr(new_class._meta, 'app_label', None) is None:
353-            # Figure out the app_label by looking one level up.
354-            # For 'django.contrib.sites.models', this would be 'sites'.
355-            model_module = sys.modules[new_class.__module__]
356-            new_class._meta.app_label = model_module.__name__.split('.')[-2]
357+            # Figure out the app_label.
358+            new_class._meta.app_label = get_app_label(new_class)
359 
360         # Bail out early if we have already created this class.
361         m = get_model(new_class._meta.app_label, name, False)
362
363=== modified file 'django/db/models/loading.py'
364--- django/db/models/loading.py
365+++ django/db/models/loading.py
366@@ -1,6 +1,6 @@
367 "Utilities for loading models and the modules that contain them."
368 
369-from django.conf import settings
370+from django.conf import settings, app
371 from django.core.exceptions import ImproperlyConfigured
372 import sys
373 import os
374@@ -18,42 +18,118 @@
375 _loaded = False  # Has the contents of settings.INSTALLED_APPS been loaded?
376                  # i.e., has get_apps() been called?
377 
378+_app_map = {}    # Map of app instances keyed by app_label.
379+_mod_map = {}    # Map of app instances keyed by app module names.
380+_apps = []       # List of app instances. Shadows _app_list.
381+
382+_set_apps = None # what INSTALLED_APPS was when _loaded was set to True
383+
384 def get_apps():
385-    "Returns a list of all installed modules that contain models."
386+    "Returns a list of all loaded applications."
387     global _app_list
388     global _loaded
389-    if not _loaded:
390+    global _app_map
391+    global _mod_map
392+    global _set_apps
393+    global _apps
394+
395+    changed = (_set_apps != settings.INSTALLED_APPS)
396+    if changed or not _loaded:
397+        if changed:
398+            _apps = []
399+            _app_map = {}
400+            _mod_map = {}
401         _loaded = True
402-        for app_name in settings.INSTALLED_APPS:
403+        _set_apps = settings.INSTALLED_APPS[:]
404+        # We first loop through, setting up the app instances and
405+        # the relevant maps so that we can find the instances by app_label
406+        # or app module name. These need to be ready because we need to be
407+        # able to get the app_label e.g. for applying to model classes.
408+        # If a model class is loaded indirectly without being in an
409+        # installed app, the app_label used will be what it is now - the
410+        # last part of the app module name.
411+        # Note that _app_map and _mod_map will contain entries even
412+        # when an app cannot be loaded by load_app.
413+        for app_entry in settings.INSTALLED_APPS:
414+            if isinstance(app_entry, basestring):
415+                the_app = app(app_entry)
416+            else:
417+                the_app = app_entry
418+            _app_map[the_app.label] = the_app
419+            _mod_map[the_app.path] = the_app
420+
421+        for app_entry in settings.INSTALLED_APPS:
422+            if isinstance(app_entry, basestring):
423+                the_app = _mod_map[app_entry]
424+            else:
425+                the_app = app_entry
426+            app_name = the_app.path
427             try:
428                 load_app(app_name)
429+                _apps.append(the_app)
430             except Exception, e:
431                 # Problem importing the app
432                 _app_errors[app_name] = e
433-    return _app_list
434+    return _apps
435+
436+def get_app_label(model_class):
437+    "Returns the app label to be used for a model class."
438+    global _mod_map
439+    key = model_class.__module__
440+    i = key.rfind('.')
441+    assert i > 0, "Model class must be defined in a package sub-module"
442+    key = key[:i]
443+    if key in _mod_map:
444+        rv = _mod_map[key].label
445+    else:
446+        i = key.rfind('.')
447+        if i < 0:
448+            rv = key
449+        else:
450+            rv = key[i + 1:]
451+    return rv
452+
453+def find_app(app_path):
454+    "Find an app instance, given the application path."
455+    get_apps()
456+    return _mod_map[app_path] # _mod_map.get(app_path, None)
457 
458 def get_app(app_label, emptyOK=False):
459-    "Returns the module containing the models for the given app_label. If the app has no models in it and 'emptyOK' is True, returns None."
460+    "Returns the app instance for the given app_label. If the app has no models in it and 'emptyOK' is True, returns None."
461     get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
462-    for app_name in settings.INSTALLED_APPS:
463-        if app_label == app_name.split('.')[-1]:
464-            mod = load_app(app_name)
465-            if mod is None:
466-                if emptyOK:
467-                    return None
468-            else:
469-                return mod
470+    if app_label not in _app_map:
471+        rv = None
472+    else:
473+        rv = _app_map[app_label]
474+    if emptyOK or rv is not None:
475+        return rv
476     raise ImproperlyConfigured, "App with label %s could not be found" % app_label
477 
478 def load_app(app_name):
479-    "Loads the app with the provided fully qualified name, and returns the model module."
480+    "Loads the app with the provided fully qualified name, and returns the models module."
481     global _app_list
482+    global _app_map
483+    global _mod_map
484+    global _apps
485     mod = __import__(app_name, {}, {}, ['models'])
486+    if app_name in _mod_map:
487+        the_app = _mod_map[app_name]
488+    else:
489+        # An app can be loaded by load_app even before get_apps is
490+        # called. In this case, we just make a new app and add it to the maps.
491+        the_app = app(app_name)
492+        _app_map[the_app.label] = the_app
493+        _mod_map[app_name] = the_app
494+        _apps.append(the_app)
495+    the_app.app_module = mod
496     if not hasattr(mod, 'models'):
497-        return None
498-    if mod.models not in _app_list:
499-        _app_list.append(mod.models)
500-    return mod.models
501+        rv = None
502+    else:
503+        rv = mod.models
504+        if rv not in _app_list:
505+            _app_list.append(rv)
506+    the_app.models_module = rv
507+    return rv
508 
509 def get_app_errors():
510     "Returns the map of known problems with the INSTALLED_APPS"
511@@ -61,18 +137,18 @@
512     get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
513     return _app_errors
514 
515-def get_models(app_mod=None):
516+def get_models(the_app=None):
517     """
518-    Given a module containing models, returns a list of the models. Otherwise
519-    returns a list of all installed models.
520+    Given an app instance, returns a list of its models. Otherwise
521+    returns a list of all loaded models.
522     """
523-    app_list = get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
524-    if app_mod:
525-        return _app_models.get(app_mod.__name__.split('.')[-2], {}).values()
526+    get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
527+    if the_app:
528+        return _app_models.get(the_app.label, {}).values()
529     else:
530         model_list = []
531-        for app_mod in app_list:
532-            model_list.extend(get_models(app_mod))
533+        for the_app in _apps:
534+            model_list.extend(get_models(the_app))
535         return model_list
536 
537 def get_model(app_label, model_name, seed_cache=True):
538
539=== modified file 'django/db/models/options.py'
540--- django/db/models/options.py
541+++ django/db/models/options.py
542@@ -1,4 +1,4 @@
543-from django.conf import settings
544+from django.conf import settings, get_installed_app_paths
545 from django.db.models.related import RelatedObject
546 from django.db.models.fields.related import ManyToManyRel
547 from django.db.models.fields import AutoField, FieldDoesNotExist
548@@ -36,7 +36,7 @@
549 
550     def contribute_to_class(self, cls, name):
551         cls._meta = self
552-        self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
553+        self.installed = re.sub('\.models$', '', cls.__module__) in get_installed_app_paths()
554         # First, construct the default values for these options.
555         self.object_name = cls.__name__
556         self.module_name = self.object_name.lower()
557
558=== modified file 'django/template/loaders/app_directories.py'
559--- django/template/loaders/app_directories.py 
560+++ django/template/loaders/app_directories.py 
561@@ -1,13 +1,13 @@
562 # Wrapper for loading templates from "template" directories in installed app packages.
563 
564-from django.conf import settings
565+from django.conf import settings, get_installed_app_paths
566 from django.core.exceptions import ImproperlyConfigured
567 from django.template import TemplateDoesNotExist
568 import os
569 
570 # At compile time, cache the directories to search.
571 app_template_dirs = []
572-for app in settings.INSTALLED_APPS:
573+for app in get_installed_app_paths():
574     i = app.rfind('.')
575     if i == -1:
576         m, a = app, None
577
578=== modified file 'django/template/loaders/eggs.py'
579--- django/template/loaders/eggs.py     
580+++ django/template/loaders/eggs.py     
581@@ -6,7 +6,7 @@
582     resource_string = None
583 
584 from django.template import TemplateDoesNotExist
585-from django.conf import settings
586+from django.conf import settings, get_installed_app_paths
587 
588 def load_template_source(template_name, template_dirs=None):
589     """
590@@ -16,7 +16,7 @@
591     """
592     if resource_string is not None:
593         pkg_name = 'templates/' + template_name
594-        for app in settings.INSTALLED_APPS:
595+        for app in get_installed_app_paths():
596             try:
597                 return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name))
598             except:
599
600=== modified file 'django/templatetags/__init__.py'
601--- django/templatetags/__init__.py     
602+++ django/templatetags/__init__.py     
603@@ -1,6 +1,6 @@
604-from django.conf import settings
605+from django.conf import settings, get_installed_app_paths
606 
607-for a in settings.INSTALLED_APPS:
608+for a in get_installed_app_paths():
609     try:
610         __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__)
611     except ImportError:
612
613=== modified file 'django/test/client.py'
614--- django/test/client.py       
615+++ django/test/client.py       
616@@ -2,7 +2,7 @@
617 import sys
618 from cStringIO import StringIO
619 from urlparse import urlparse
620-from django.conf import settings
621+from django.conf import settings, get_installed_app_paths
622 from django.contrib.auth import authenticate, login
623 from django.contrib.sessions.models import Session
624 from django.contrib.sessions.middleware import SessionWrapper
625@@ -128,7 +128,7 @@
626 
627     def _session(self):
628         "Obtain the current session variables"
629-        if 'django.contrib.sessions' in settings.INSTALLED_APPS:
630+        if 'django.contrib.sessions' in get_installed_app_paths():
631             cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
632             if cookie:
633                 return SessionWrapper(cookie.value)
634@@ -228,7 +228,7 @@
635         are incorrect, or if the Sessions framework is not available.
636         """
637         user = authenticate(**credentials)
638-        if user and 'django.contrib.sessions' in settings.INSTALLED_APPS:
639+        if user and 'django.contrib.sessions' in get_installed_app_paths():
640             obj = Session.objects.get_new_session_object()
641 
642             # Create a fake request to store login details
643@@ -251,4 +251,4 @@
644             return True
645         else:
646             return False
647-           
648\ No newline at end of file
649+           
650
651=== modified file 'django/utils/translation/trans_real.py'
652--- django/utils/translation/trans_real.py     
653+++ django/utils/translation/trans_real.py     
654@@ -107,7 +107,7 @@
655     if t is not None:
656         return t
657 
658-    from django.conf import settings
659+    from django.conf import settings, get_installed_app_paths
660 
661     # set up the right translation class
662     klass = DjangoTranslation
663@@ -160,7 +160,7 @@
664         if projectpath and os.path.isdir(projectpath):
665             res = _merge(projectpath)
666 
667-        for appname in settings.INSTALLED_APPS:
668+        for appname in get_installed_app_paths():
669             p = appname.rfind('.')
670             if p >= 0:
671                 app = getattr(__import__(appname[:p], {}, {}, [appname[p+1:]]), appname[p+1:])
672
673=== modified file 'django/views/i18n.py'
674--- django/views/i18n.py       
675+++ django/views/i18n.py       
676@@ -1,7 +1,7 @@
677 from django import http
678 from django.utils.translation import check_for_language, activate, to_locale, get_language
679 from django.utils.text import javascript_quote
680-from django.conf import settings
681+from django.conf import settings, get_installed_app_paths
682 import os
683 import gettext as gettext_module
684 
685@@ -104,7 +104,7 @@
686         packages = ['django.conf']
687     if type(packages) in (str, unicode):
688         packages = packages.split('+')
689-    packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS]
690+    packages = [p for p in packages if p == 'django.conf' or p in get_installed_app_paths()]
691     default_locale = to_locale(settings.LANGUAGE_CODE)
692     locale = to_locale(get_language())
693     t = {}
694
695=== modified file 'docs/settings.txt'
696--- docs/settings.txt   
697+++ docs/settings.txt   
698@@ -469,9 +469,29 @@
699 
700 Default: ``()`` (Empty tuple)
701 
702-A tuple of strings designating all applications that are enabled in this Django
703-installation. Each string should be a full Python path to a Python package that
704-contains a Django application, as created by `django-admin.py startapp`_.
705+A tuple of entries designating all applications that are enabled in this Django
706+installation. Each entry should be one of the following:
707+
708+    * A string which is a full Python path to a Python package that contains a
709+      Django application, as created by `django-admin.py startapp`_.
710+    * A string which is a full Python path to a Python package which contains
711+      multiple applications, with an appended ``.*``. This entry is replaced
712+      by entries for the contained applications. For example, a wildcard
713+      entry of the form ``django.contrib.*`` would be replaced by
714+      multiple entries for ``django.contrib.admin``, ``django.contrib.auth``
715+      etc.
716+    * An ``app`` configuration directive (to use it, you need to add
717+      ``from django.conf import app`` to your ``settings`` file). This takes
718+      the form ``app(path, label=None, verbose_name=None)``. The ``path``
719+      argument must specify the full Python path to a Python package that
720+      contains a Django application. The ``label`` argument, if specified,
721+      provides a unique application label (used to disambiguate different
722+      third-party applications with conflicting default label values). If this
723+      value is not specified, the last portion of the application path is used
724+      as the default label. The ``verbose_name`` argument, if specified, is
725+      used to provide a descriptive name for the application in the admin
726+      screens. If this value is not specified, the last portion of the
727+      application path is used as the default value.
728 
729 .. _django-admin.py startapp: ../django-admin/#startapp-appname
730 
731
732=== modified file 'tests/runtests.py'
733--- tests/runtests.py   
734+++ tests/runtests.py   
735@@ -2,6 +2,7 @@
736 
737 import os, sys, traceback
738 import unittest
739+from django.conf import app
740 
741 MODEL_TESTS_DIR_NAME = 'modeltests'
742 REGRESSION_TESTS_DIR_NAME = 'regressiontests'
743@@ -13,7 +14,8 @@
744 
745 ALWAYS_INSTALLED_APPS = [
746     'django.contrib.contenttypes',
747-    'django.contrib.auth',
748+    #We need to use the same app label, otherwise fixtures will break...
749+    app('django.contrib.auth', 'auth', 'Authentication'),
750     'django.contrib.sites',
751     'django.contrib.flatpages',
752     'django.contrib.redirects',
753@@ -48,16 +50,16 @@
754 
755     def runTest(self):
756         from django.core import management
757-        from django.db.models.loading import load_app
758+        from django.db.models.loading import load_app, find_app
759         from cStringIO import StringIO
760 
761         try:
762             module = load_app(self.model_label)
763         except Exception, e:
764-            self.fail('Unable to load invalid model module')
765+            self.fail('Unable to load invalid application %s: %s' % (self.model_label, e))
766 
767         s = StringIO()
768-        count = management.get_validation_errors(s, module)
769+        count = management.get_validation_errors(s, find_app(self.model_label))
770         s.seek(0)
771         error_log = s.read()
772         actual = error_log.split('\n')
773