Index: django/test/simple.py
===================================================================
--- django/test/simple.py	(revision 6453)
+++ django/test/simple.py	(working copy)
@@ -64,7 +64,7 @@
             suite.addTest(test_module.suite())
         else:
             suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_module))
-            try:            
+            try:
                 suite.addTest(doctest.DocTestSuite(test_module, 
                                                    checker=doctestOutputChecker,
                                                    runner=DocTestRunner))
@@ -130,10 +130,14 @@
                 suite.addTest(build_test(label))
             else:
                 app = get_app(label)
-                suite.addTest(build_suite(app))
+                mod = app.models_module
+                if mod:
+                  suite.addTest(build_suite(mod))
     else:
         for app in get_apps():
-            suite.addTest(build_suite(app))
+            mod = app.models_module
+            if mod:
+                suite.addTest(build_suite(mod))
     
     for test in extra_tests:
         suite.addTest(test)
Index: django/test/client.py
===================================================================
--- django/test/client.py	(revision 6453)
+++ django/test/client.py	(working copy)
@@ -2,7 +2,7 @@
 import sys
 from cStringIO import StringIO
 from urlparse import urlparse
-from django.conf import settings
+from django.conf import settings, get_installed_app_paths
 from django.contrib.auth import authenticate, login
 from django.core.handlers.base import BaseHandler
 from django.core.handlers.wsgi import WSGIRequest
@@ -129,7 +129,7 @@
 
     def _session(self):
         "Obtain the current session variables"
-        if 'django.contrib.sessions' in settings.INSTALLED_APPS:
+        if 'django.contrib.sessions' in get_installed_app_paths():
             engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
             cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
             if cookie:
@@ -245,7 +245,7 @@
         not available.
         """
         user = authenticate(**credentials)
-        if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
+        if user and user.is_active and 'django.contrib.sessions' in get_installed_app_paths():
             engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
 
             # Create a fake request to store login details
Index: django/db/models/base.py
===================================================================
--- django/db/models/base.py	(revision 6453)
+++ django/db/models/base.py	(working copy)
@@ -8,7 +8,7 @@
 from django.db.models.options import Options, AdminOptions
 from django.db import connection, transaction
 from django.db.models import signals
-from django.db.models.loading import register_models, get_model
+from django.db.models.loading import register_models, get_model, get_app_label
 from django.dispatch import dispatcher
 from django.utils.datastructures import SortedDict
 from django.utils.functional import curry
@@ -43,12 +43,9 @@
                 new_class._meta.parents.append(base)
                 new_class._meta.parents.extend(base._meta.parents)
 
-
         if getattr(new_class._meta, 'app_label', None) is None:
-            # Figure out the app_label by looking one level up.
-            # For 'django.contrib.sites.models', this would be 'sites'.
-            model_module = sys.modules[new_class.__module__]
-            new_class._meta.app_label = model_module.__name__.split('.')[-2]
+            # Figure out the app_label.
+            new_class._meta.app_label = get_app_label(new_class)
 
         # Bail out early if we have already created this class.
         m = get_model(new_class._meta.app_label, name, False)
Index: django/db/models/options.py
===================================================================
--- django/db/models/options.py	(revision 6453)
+++ django/db/models/options.py	(working copy)
@@ -1,4 +1,4 @@
-from django.conf import settings
+from django.conf import settings, get_installed_app_paths
 from django.db.models.related import RelatedObject
 from django.db.models.fields.related import ManyToManyRel
 from django.db.models.fields import AutoField, FieldDoesNotExist
@@ -39,7 +39,7 @@
 
     def contribute_to_class(self, cls, name):
         cls._meta = self
-        self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
+        self.installed = re.sub('\.models$', '', cls.__module__) in get_installed_app_paths()
         # First, construct the default values for these options.
         self.object_name = cls.__name__
         self.module_name = self.object_name.lower()
Index: django/db/models/loading.py
===================================================================
--- django/db/models/loading.py	(revision 6453)
+++ django/db/models/loading.py	(working copy)
@@ -1,13 +1,13 @@
 "Utilities for loading models and the modules that contain them."
 
-from django.conf import settings
+from django.conf import settings, app
 from django.core.exceptions import ImproperlyConfigured
 import sys
 import os
 import threading
 
 __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
-        'load_app', 'app_cache_ready')
+        'load_app', 'app_cache_ready', 'find_app')
 
 class AppCache(object):
     """
@@ -18,6 +18,7 @@
     # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531.
     __shared_state = dict(
         # Keys of app_store are the model modules for each application.
+        # Values are integers indicating the order of loading.
         app_store = {},
 
         # Mapping of app_labels to a dictionary of model names to model code.
@@ -26,6 +27,16 @@
         # Mapping of app_labels to errors raised when trying to import the app.
         app_errors = {},
 
+
+        # Mapping of app_labels to app instances.
+        app_map = {},
+
+        # Mapping of app module names to app instances.
+        mod_map = {},
+
+        # List of app instances.
+        app_instances = [],
+
         # -- Everything below here is only used when populating the cache --
         loaded = False,
         handled = {},
@@ -49,13 +60,42 @@
         try:
             if self.loaded:
                 return
-            for app_name in settings.INSTALLED_APPS:
+            # We first loop through, setting up the app instances and
+            # the relevant maps so that we can find the instances by app_label
+            # or app module name. These need to be ready because we need to be
+            # able to get the app_label e.g. for applying to model classes.
+            # If a model class is loaded indirectly without being in an
+            # installed app, the app_label used will be what it is now - the
+            # last part of the app module name.
+            # Note that app_map and mod_map will contain entries even
+            # when an app cannot be loaded by load_app.
+            for app_entry in settings.INSTALLED_APPS:
+                if isinstance(app_entry, basestring):
+                    the_app = app(app_entry)
+                else:
+                    the_app = app_entry
+                self.app_map[the_app.label] = the_app
+                self.mod_map[the_app.path] = the_app
+
+            for app_entry in settings.INSTALLED_APPS:
+                if isinstance(app_entry, basestring):
+                    the_app = self.mod_map[app_entry]
+                else:
+                    the_app = app_entry
+                app_name = the_app.path
                 if app_name in self.handled:
                     continue
-                self.load_app(app_name, True)
+                try:
+                    self.load_app(app_name, True)
+                    self.app_instances.append(the_app)
+                except Exception, e:
+                    # Problem importing the app
+                    self.app_errors[app_name] = e
             if not self.nesting_level:
                 for app_name in self.postponed:
+                    the_app = self.mod_map[app_name]
                     self.load_app(app_name)
+                    self.app_instances.append(the_app)                    
                 self.loaded = True
         finally:
             self.write_lock.release()
@@ -69,6 +109,16 @@
         self.nesting_level += 1
         mod = __import__(app_name, {}, {}, ['models'])
         self.nesting_level -= 1
+        if app_name in self.mod_map:
+            the_app = self.mod_map[app_name]
+        else:
+            # An app can be loaded by load_app even before get_apps is
+            # called. In this case, we just make a new app and add it to the maps.
+            the_app = app(app_name)
+            self.app_map[the_app.label] = the_app
+            self.mod_map[app_name] = the_app
+            self.app_instances.append(the_app)
+        the_app.app_module = mod
         if not hasattr(mod, 'models'):
             if can_postpone:
                 # Either the app has no models, or the package is still being
@@ -76,10 +126,13 @@
                 # We will check again once all the recursion has finished (in
                 # populate).
                 self.postponed.append(app_name)
-            return None
-        if mod.models not in self.app_store:
-            self.app_store[mod.models] = len(self.app_store)
-        return mod.models
+            rv = None
+        else:
+            rv = mod.models
+            if rv not in self.app_store:
+                self.app_store[rv] = len(self.app_store)
+        the_app.models_module = rv
+        return rv
 
     def app_cache_ready(self):
         """
@@ -99,45 +152,61 @@
         # list page, for example.
         apps = [(v, k) for k, v in self.app_store.items()]
         apps.sort()
-        return [elt[1] for elt in apps]
+        #return [elt[1] for elt in apps]
+        return self.app_instances
 
+    def get_app_label(self, model_class):
+        "Returns the app label to be used for a model class."
+        key = model_class.__module__
+        i = key.rfind('.')
+        assert i > 0, "Model class must be defined in a package sub-module"
+        key = key[:i]
+        if key in self.mod_map:
+            rv = self.mod_map[key].label
+        else:
+            i = key.rfind('.')
+            if i < 0:
+                rv = key
+            else:
+                rv = key[i + 1:]
+        return rv
+    
+    def find_app(self, app_path):
+        "Find an app instance, given the application path."
+        self._populate()
+        return self.mod_map[app_path]
+        
     def get_app(self, app_label, emptyOK=False):
         """
-        Returns the module containing the models for the given app_label. If
+        Returns the app instance for the given app_label. If
         the app has no models in it and 'emptyOK' is True, returns None.
         """
         self._populate()
-        self.write_lock.acquire()
-        try:
-            for app_name in settings.INSTALLED_APPS:
-                if app_label == app_name.split('.')[-1]:
-                    mod = self.load_app(app_name, False)
-                    if mod is None:
-                        if emptyOK:
-                            return None
-                    else:
-                        return mod
-            raise ImproperlyConfigured, "App with label %s could not be found" % app_label
-        finally:
-            self.write_lock.release()
+        if app_label not in self.app_map:
+            rv = None
+        else:
+            rv = self.app_map[app_label]
+        if emptyOK or rv is not None:
+            return rv
+        raise ImproperlyConfigured, "App with label %s could not be found" % app_label
 
     def get_app_errors(self):
         "Returns the map of known problems with the INSTALLED_APPS."
         self._populate()
         return self.app_errors
 
-    def get_models(self, app_mod=None):
+    def get_models(self, the_app=None):
         """
-        Given a module containing models, returns a list of the models.
+        Given an app instance, returns a list of its models.
         Otherwise returns a list of all installed models.
         """
         self._populate()
-        if app_mod:
-            return self.app_models.get(app_mod.__name__.split('.')[-2], {}).values()
+        if the_app:
+            return self.app_models.get(the_app.label, {}).values()
         else:
             model_list = []
-            for app_entry in self.app_models.itervalues():
-                model_list.extend(app_entry.values())
+            for the_app in self.app_instances:
+                model_list.extend(get_models(the_app))
             return model_list
 
     def get_model(self, app_label, model_name, seed_cache=True):
@@ -185,3 +254,5 @@
 register_models = cache.register_models
 load_app = cache.load_app
 app_cache_ready = cache.app_cache_ready
+find_app = cache.find_app
+get_app_label = cache.get_app_label
Index: django/db/models/__init__.py
===================================================================
--- django/db/models/__init__.py	(revision 6453)
+++ django/db/models/__init__.py	(working copy)
@@ -2,7 +2,7 @@
 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
 from django.core import validators
 from django.db import connection
-from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
+from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models, find_app
 from django.db.models.query import Q
 from django.db.models.manager import Manager
 from django.db.models.base import Model, AdminOptions
Index: django/conf/__init__.py
===================================================================
--- django/conf/__init__.py	(revision 6453)
+++ django/conf/__init__.py	(working copy)
@@ -99,13 +99,13 @@
         # of all those apps.
         new_installed_apps = []
         for app in self.INSTALLED_APPS:
-            if app.endswith('.*'):
+            if not isinstance(app, basestring) or not app.endswith('.*'):
+                new_installed_apps.append(app)
+            else:
                 appdir = os.path.dirname(__import__(app[:-2], {}, {}, ['']).__file__)
                 for d in os.listdir(appdir):
                     if d.isalpha() and os.path.isdir(os.path.join(appdir, d)):
                         new_installed_apps.append('%s.%s' % (app[:-2], d))
-            else:
-                new_installed_apps.append(app)
         self.INSTALLED_APPS = new_installed_apps
 
         if hasattr(time, 'tzset'):
@@ -150,3 +150,26 @@
     return gettext(*args)
 
 __builtins__['_'] = first_time_gettext
+
+class app(object):
+    """Configuration directive for specifying an app."""
+    def __init__(self, path, app_label=None, verbose_name=None):
+        self.path = path
+        # if name isn't specified, get the last part of the Python dotted path
+        self.label = app_label or path.split('.')[-1]
+        self.verbose_name = verbose_name or self.label
+        self.app_module = None    # will be filled in by loading.py
+        self.models_module = None # will be filled in by loading.py
+
+    def __repr__(self):
+        return "<app: %s>" % self.path
+
+def get_installed_app_paths():
+    "Return the paths of all entries in settings.INSTALLED_APPS."
+    rv = []
+    for a in settings.INSTALLED_APPS:
+        if isinstance(a, basestring):
+            rv.append(a)
+        else:
+            rv.append(a.path)
+    return rv
Index: django/core/management/commands/loaddata.py
===================================================================
--- django/core/management/commands/loaddata.py	(revision 6453)
+++ django/core/management/commands/loaddata.py	(working copy)
@@ -45,7 +45,7 @@
         transaction.enter_transaction_management()
         transaction.managed(True)
 
-        app_fixtures = [os.path.join(os.path.dirname(app.__file__), 'fixtures') for app in get_apps()]
+        app_fixtures = [os.path.join(os.path.dirname(app.models_module.__file__), 'fixtures') for app in get_apps() if app.models_module]
         for fixture_label in fixture_labels:
             parts = fixture_label.split('.')
             if len(parts) == 1:
Index: django/core/management/commands/flush.py
===================================================================
--- django/core/management/commands/flush.py	(revision 6453)
+++ django/core/management/commands/flush.py	(working copy)
@@ -1,3 +1,4 @@
+from django.conf import get_installed_app_paths
 from django.core.management.base import NoArgsCommand, CommandError
 from django.core.management.color import no_style
 from optparse import make_option
@@ -25,7 +26,7 @@
 
         # Import the 'management' module within each installed app, to register
         # dispatcher events.
-        for app_name in settings.INSTALLED_APPS:
+        for app_name in get_installed_app_paths():
             try:
                 __import__(app_name + '.management', {}, {}, [''])
             except ImportError:
Index: django/core/management/commands/syncdb.py
===================================================================
--- django/core/management/commands/syncdb.py	(revision 6453)
+++ django/core/management/commands/syncdb.py	(working copy)
@@ -1,3 +1,4 @@
+from django.conf import get_installed_app_paths
 from django.core.management.base import NoArgsCommand
 from django.core.management.color import no_style
 from optparse import make_option
@@ -30,7 +31,7 @@
 
         # Import the 'management' module within each installed app, to register
         # dispatcher events.
-        for app_name in settings.INSTALLED_APPS:
+        for app_name in get_installed_app_paths():
             try:
                 __import__(app_name + '.management', {}, {}, [''])
             except ImportError:
@@ -53,7 +54,7 @@
 
         # Create the tables for each model
         for app in models.get_apps():
-            app_name = app.__name__.split('.')[-2]
+            app_name = app.label
             model_list = models.get_models(app)
             for model in model_list:
                 # Create the model's database table, if it doesn't already exist.
@@ -76,7 +77,7 @@
         # Create the m2m tables. This must be done after all tables have been created
         # to ensure that all referred tables will exist.
         for app in models.get_apps():
-            app_name = app.__name__.split('.')[-2]
+            app_name = app.label
             model_list = models.get_models(app)
             for model in model_list:
                 if model in created_models:
@@ -96,7 +97,7 @@
         # Install custom SQL for the app (but only if this
         # is a model we've just created)
         for app in models.get_apps():
-            app_name = app.__name__.split('.')[-2]
+            app_name = app.label
             for model in models.get_models(app):
                 if model in created_models:
                     custom_sql = custom_sql_for_model(model)
@@ -115,7 +116,7 @@
 
         # Install SQL indicies for all newly created models
         for app in models.get_apps():
-            app_name = app.__name__.split('.')[-2]
+            app_name = app.label
             for model in models.get_models(app):
                 if model in created_models:
                     index_sql = sql_indexes_for_model(model, self.style)
Index: django/core/management/__init__.py
===================================================================
--- django/core/management/__init__.py	(revision 6453)
+++ django/core/management/__init__.py	(working copy)
@@ -1,4 +1,5 @@
 import django
+from django.conf import get_installed_app_paths
 from django.core.management.base import BaseCommand, CommandError, handle_default_options 
 from optparse import OptionParser
 import os
@@ -79,7 +80,7 @@
         if load_user_commands:
             # Get commands from all installed apps
             from django.conf import settings
-            for app_name in settings.INSTALLED_APPS:
+            for app_name in get_installed_app_paths():
                 try:
                     path = find_management_module(app_name)
                     _commands.update(dict([(name, app_name) 
Index: django/core/management/sql.py
===================================================================
--- django/core/management/sql.py	(revision 6453)
+++ django/core/management/sql.py	(working copy)
@@ -398,7 +398,7 @@
     from django.conf import settings
 
     opts = model._meta
-    app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))
+    app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).models_module.__file__), 'sql'))
     output = []
 
     # Some backends can't execute more than one SQL statement at a time,
@@ -449,7 +449,7 @@
     from django.dispatch import dispatcher
     # Emit the post_sync signal for every application.
     for app in models.get_apps():
-        app_name = app.__name__.split('.')[-2]
+        app_name = app.label
         if verbosity >= 2:
             print "Running post-sync handlers for application", app_name
         dispatcher.send(signal=models.signals.post_syncdb, sender=app,
Index: django/templatetags/__init__.py
===================================================================
--- django/templatetags/__init__.py	(revision 6453)
+++ django/templatetags/__init__.py	(working copy)
@@ -1,6 +1,6 @@
-from django.conf import settings
+from django.conf import settings, get_installed_app_paths
 
-for a in settings.INSTALLED_APPS:
+for a in get_installed_app_paths():
     try:
         __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__)
     except ImportError:
Index: django/views/i18n.py
===================================================================
--- django/views/i18n.py	(revision 6453)
+++ django/views/i18n.py	(working copy)
@@ -1,7 +1,7 @@
 from django import http
 from django.utils.translation import check_for_language, activate, to_locale, get_language
 from django.utils.text import javascript_quote
-from django.conf import settings
+from django.conf import settings, get_installed_app_paths
 import os
 import gettext as gettext_module
 
@@ -110,7 +110,7 @@
         packages = ['django.conf']
     if type(packages) in (str, unicode):
         packages = packages.split('+')
-    packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS]
+    packages = [p for p in packages if p == 'django.conf' or p in get_installed_app_paths()]
     default_locale = to_locale(settings.LANGUAGE_CODE)
     locale = to_locale(get_language())
     t = {}
Index: django/utils/translation/trans_real.py
===================================================================
--- django/utils/translation/trans_real.py	(revision 6453)
+++ django/utils/translation/trans_real.py	(working copy)
@@ -108,7 +108,7 @@
     if t is not None:
         return t
 
-    from django.conf import settings
+    from django.conf import settings, get_installed_app_paths
 
     # set up the right translation class
     klass = DjangoTranslation
@@ -161,7 +161,7 @@
         if projectpath and os.path.isdir(projectpath):
             res = _merge(projectpath)
 
-        for appname in settings.INSTALLED_APPS:
+        for appname in get_installed_app_paths():
             p = appname.rfind('.')
             if p >= 0:
                 app = getattr(__import__(appname[:p], {}, {}, [appname[p+1:]]), appname[p+1:])
Index: django/contrib/sites/management.py
===================================================================
--- django/contrib/sites/management.py	(revision 6453)
+++ django/contrib/sites/management.py	(working copy)
@@ -3,9 +3,8 @@
 """
 
 from django.dispatch import dispatcher
-from django.db.models import signals
+from django.db.models import signals, find_app
 from django.contrib.sites.models import Site
-from django.contrib.sites import models as site_app
 
 def create_default_site(app, created_models, verbosity):
     if Site in created_models:
@@ -14,4 +13,4 @@
         s = Site(domain="example.com", name="example.com")
         s.save()
 
-dispatcher.connect(create_default_site, sender=site_app, signal=signals.post_syncdb)
+dispatcher.connect(create_default_site, sender=find_app('django.contrib.sites'), signal=signals.post_syncdb)
Index: django/contrib/admin/templatetags/adminapplist.py
===================================================================
--- django/contrib/admin/templatetags/adminapplist.py	(revision 6453)
+++ django/contrib/admin/templatetags/adminapplist.py	(working copy)
@@ -19,7 +19,7 @@
             app_models = get_models(app)
             if not app_models:
                 continue
-            app_label = app_models[0]._meta.app_label
+            app_label = app.label
 
             has_module_perms = user.has_module_perms(app_label)
 
@@ -50,7 +50,7 @@
                     model_list = [x for key, x in decorated]
 
                     app_list.append({
-                        'name': app_label.title(),
+                        'name': _(app.verbose_name).title(),
                         'has_module_perms': has_module_perms,
                         'models': model_list,
                     })
@@ -63,7 +63,7 @@
     has at least one permission.
 
     Syntax::
-    
+
         {% get_admin_app_list as [context_var_containing_app_list] %}
 
     Example usage::
Index: django/contrib/admin/views/doc.py
===================================================================
--- django/contrib/admin/views/doc.py	(revision 6453)
+++ django/contrib/admin/views/doc.py	(working copy)
@@ -160,11 +160,11 @@
 
     # Get the model class.
     try:
-        app_mod = models.get_app(app_label)
+        app = models.get_app(app_label)
     except ImproperlyConfigured:
         raise Http404, _("App %r not found") % app_label
     model = None
-    for m in models.get_models(app_mod):
+    for m in models.get_models(app):
         if m._meta.object_name.lower() == model_name:
             model = m
             break
Index: django/contrib/contenttypes/management.py
===================================================================
--- django/contrib/contenttypes/management.py	(revision 6453)
+++ django/contrib/contenttypes/management.py	(working copy)
@@ -9,7 +9,7 @@
     entries that no longer have a matching model class.
     """
     ContentType.objects.clear_cache()
-    content_types = list(ContentType.objects.filter(app_label=app.__name__.split('.')[-2]))
+    content_types = list(ContentType.objects.filter(app_label=app.label))
     app_models = get_models(app)
     if not app_models:
         return
Index: django/contrib/auth/management.py
===================================================================
--- django/contrib/auth/management.py	(revision 6453)
+++ django/contrib/auth/management.py	(working copy)
@@ -3,8 +3,7 @@
 """
 
 from django.dispatch import dispatcher
-from django.db.models import get_models, signals
-from django.contrib.auth import models as auth_app
+from django.db.models import get_models, signals, find_app
 
 def _get_permission_codename(action, opts):
     return u'%s_%s' % (action, opts.object_name.lower())
@@ -46,4 +45,4 @@
             break
 
 dispatcher.connect(create_permissions, signal=signals.post_syncdb)
-dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)
+dispatcher.connect(create_superuser, sender=find_app('django.contrib.auth'), signal=signals.post_syncdb)
Index: django/contrib/auth/models.py
===================================================================
--- django/contrib/auth/models.py	(revision 6453)
+++ django/contrib/auth/models.py	(working copy)
@@ -303,6 +303,21 @@
                 raise SiteProfileNotAvailable
         return self._profile_cache
 
+    def __getattribute__(self, attrname):
+        if attrname == '_profile_cache':
+            return models.Model.__getattribute__(self, attrname)
+        try:
+            rv = models.Model.__getattribute__(self, attrname)
+        except AttributeError, e:
+            if not settings.AUTH_PROFILE_MODULE:
+                raise
+            try:
+                profile = self.get_profile()
+                rv = getattr(profile, attrname)
+            except SiteProfileNotAvailable:
+                raise e
+        return rv
+
 class Message(models.Model):
     """
     The message system is a lightweight way to queue messages for given
Index: django/bin/make-messages.py
===================================================================
--- django/bin/make-messages.py	(revision 6453)
+++ django/bin/make-messages.py	(working copy)
@@ -139,6 +139,22 @@
                 print "errors happened while running msguniq"
                 print errors
                 sys.exit(8)
+            # Try importing the local settings. This will work if you're
+            # in the project directory
+            sys.path.insert(0, '.')
+            try:
+                import settings
+                apps = settings.INSTALLED_APPS
+            except (ImportError, AttributeError):
+                apps = []
+            del sys.path[0]
+            # Now look for all applications which have a verbose name
+            # which is different from the default. If different, add
+            # an internationalization string.
+            for app in apps:
+                if (not isinstance(app, basestring)) and (app.verbose_name != app.path.split('.')[-1]):
+                    s = '\nmsgid "%s"\nmsgstr ""\n' % app.verbose_name
+                    msgs += s
             open(potfile, 'w').write(msgs)
             if os.path.exists(pofile):
                 (stdin, stdout, stderr) = os.popen3('msgmerge -q "%s" "%s"' % (pofile, potfile), 'b')
Index: django/template/loaders/app_directories.py
===================================================================
--- django/template/loaders/app_directories.py	(revision 6453)
+++ django/template/loaders/app_directories.py	(working copy)
@@ -5,14 +5,14 @@
 
 import os
 
-from django.conf import settings
+from django.conf import settings, get_installed_app_paths
 from django.core.exceptions import ImproperlyConfigured
 from django.template import TemplateDoesNotExist
 from django.utils._os import safe_join
 
 # At compile time, cache the directories to search.
 app_template_dirs = []
-for app in settings.INSTALLED_APPS:
+for app in get_installed_app_paths():
     i = app.rfind('.')
     if i == -1:
         m, a = app, None
Index: django/template/loaders/eggs.py
===================================================================
--- django/template/loaders/eggs.py	(revision 6453)
+++ django/template/loaders/eggs.py	(working copy)
@@ -6,7 +6,7 @@
     resource_string = None
 
 from django.template import TemplateDoesNotExist
-from django.conf import settings
+from django.conf import settings, get_installed_app_paths
 
 def load_template_source(template_name, template_dirs=None):
     """
@@ -16,7 +16,7 @@
     """
     if resource_string is not None:
         pkg_name = 'templates/' + template_name
-        for app in settings.INSTALLED_APPS:
+        for app in get_installed_app_paths():
             try:
                 return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name)).decode(settings.FILE_CHARSET)
             except:
Index: tests/runtests.py
===================================================================
--- tests/runtests.py	(revision 6453)
+++ tests/runtests.py	(working copy)
@@ -2,6 +2,7 @@
 
 import os, sys, traceback
 import unittest
+from django.conf import app, get_installed_app_paths
 
 import django.contrib as contrib
 
@@ -23,7 +24,8 @@
 
 ALWAYS_INSTALLED_APPS = [
     'django.contrib.contenttypes',
-    'django.contrib.auth',
+    #We need to use the same app label, otherwise fixtures will break...
+    app('django.contrib.auth', 'auth', 'Authentication'),
     'django.contrib.sites',
     'django.contrib.flatpages',
     'django.contrib.redirects',
@@ -58,13 +60,14 @@
 
     def runTest(self):
         from django.core.management.validation import get_validation_errors
-        from django.db.models.loading import load_app
+        from django.db.models.loading import load_app, find_app
         from cStringIO import StringIO
 
         try:
             module = load_app(self.model_label)
+            app = find_app(self.model_label)
         except Exception, e:
-            self.fail('Unable to load invalid model module')
+            self.fail('Unable to load invalid application %s: %s' % (self.model_label, e))
 
         # Make sure sys.stdout is not a tty so that we get errors without
         # coloring attached (makes matching the results easier). We restore
@@ -72,7 +75,7 @@
         orig_stdout = sys.stdout
         s = StringIO()
         sys.stdout = s
-        count = get_validation_errors(s, module)
+        count = get_validation_errors(s, app)
         sys.stdout = orig_stdout
         s.seek(0)
         error_log = s.read()
@@ -127,7 +130,7 @@
                     print "Importing model %s" % model_name
                 mod = load_app(model_label)
                 if mod:
-                    if model_label not in settings.INSTALLED_APPS:
+                    if model_label not in get_installed_app_paths():
                         settings.INSTALLED_APPS.append(model_label)
         except Exception, e:
             sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
Index: docs/settings.txt
===================================================================
--- docs/settings.txt	(revision 6453)
+++ docs/settings.txt	(working copy)
@@ -501,10 +501,30 @@
 
 Default: ``()`` (Empty tuple)
 
-A tuple of strings designating all applications that are enabled in this Django
-installation. Each string should be a full Python path to a Python package that
-contains a Django application, as created by `django-admin.py startapp`_.
+A tuple of entries designating all applications that are enabled in this Django
+installation. Each entry should be one of the following:
 
+    * A string which is a full Python path to a Python package that contains a
+      Django application, as created by `django-admin.py startapp`_.
+    * A string which is a full Python path to a Python package which contains
+      multiple applications, with an appended ``.*``. This entry is replaced
+      by entries for the contained applications. For example, a wildcard
+      entry of the form ``django.contrib.*`` would be replaced by
+      multiple entries for ``django.contrib.admin``, ``django.contrib.auth``
+      etc.
+    * An ``app`` configuration directive (to use it, you need to add
+      ``from django.conf import app`` to your ``settings`` file). This takes
+      the form ``app(path, label=None, verbose_name=None)``. The ``path``
+      argument must specify the full Python path to a Python package that
+      contains a Django application. The ``label`` argument, if specified,
+      provides a unique application label (used to disambiguate different
+      third-party applications with conflicting default label values). If this
+      value is not specified, the last portion of the application path is used
+      as the default label. The ``verbose_name`` argument, if specified, is
+      used to provide a descriptive name for the application in the admin
+      screens. If this value is not specified, the last portion of the
+      application path is used as the default value.
+
 .. _django-admin.py startapp: ../django-admin/#startapp-appname
 
 INTERNAL_IPS
