
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -375,6 +375,15 @@ LOGIN_REDIRECT_URL = '/accounts/profile/'
 # The number of days a password reset link is valid for
 PASSWORD_RESET_TIMEOUT_DAYS = 3
 
+# The class to use as the default AUTH_USER for the authentication system.
+AUTH_USER_MODULE = 'django.contrib.auth.user_model.User'
+
+# Trigger superuser creation after syncdb
+AUTH_AUTO_CREATE_SUPERUSER = True
+
+# Trigger permissions creation after syncdb
+AUTH_AUTO_CREATE_PERMISSIONS = True
+
 ###########
 # TESTING #
 ###########


--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -1,5 +1,5 @@
 
-from django.contrib.auth.models import User, Group
+from django.contrib.auth.models import Permission, User, Group, Message
 from django.core.exceptions import PermissionDenied
 from django import template
 from django.shortcuts import render_to_response, get_object_or_404
@@ -9,6 +9,7 @@ from django.http import HttpResponseRedirect
 from django.utils.translation import ugettext, ugettext_lazy as _
 from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm
 from django.contrib import admin
+from django.conf import settings, global_settings
 
 class GroupAdmin(admin.ModelAdmin):
     search_fields = ('name',)
@@ -108,7 +111,7 @@ class UserAdmin(admin.ModelAdmin):
             'root_path': self.admin_site.root_path,
         }, context_instance=RequestContext(request))
 
-
 admin.site.register(Group, GroupAdmin)
-admin.site.register(User, UserAdmin)
+if settings.AUTH_USER_MODULE == global_settings.AUTH_USER_MODULE:
+    admin.site.register(User, UserAdmin)
 
diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py
index 01fa524..15abbec 100644
--- a/django/contrib/auth/management/__init__.py
+++ b/django/contrib/auth/management/__init__.py
@@ -4,6 +4,7 @@ Creates permissions for all installed apps that need permissions.
 
 from django.db.models import get_models, signals
 from django.contrib.auth import models as auth_app
+from django.conf import settings
 
 def _get_permission_codename(action, opts):
     return u'%s_%s' % (action, opts.object_name.lower())
@@ -44,7 +45,9 @@ def create_superuser(app, created_models, verbosity, **kwargs):
                 call_command("createsuperuser", interactive=True)
             break
 
-signals.post_syncdb.connect(create_permissions,
-    dispatch_uid = "django.contrib.auth.management.create_permissions")
-signals.post_syncdb.connect(create_superuser,
-    sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser")
+if settings.AUTH_AUTO_CREATE_PERMISSIONS:
+    signals.post_syncdb.connect(create_permissions,
+        dispatch_uid = "django.contrib.auth.management.create_permissions")
+if settings.AUTH_AUTO_CREATE_SUPERUSER:
+    signals.post_syncdb.connect(create_superuser,
+        sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser")
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 4d3189b..57b36ba 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -5,6 +5,7 @@ from django.db.models.manager import EmptyManager
 from django.contrib.contenttypes.models import ContentType
 from django.utils.encoding import smart_str
 from django.utils.translation import ugettext_lazy as _
+from django.conf import settings, global_settings
 import datetime
 import urllib
 
@@ -102,62 +103,15 @@ class Group(models.Model):
     def __unicode__(self):
         return self.name
 
-class UserManager(models.Manager):
-    def create_user(self, username, email, password=None):
-        "Creates and saves a User with the given username, e-mail and password."
-        now = datetime.datetime.now()
-        user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now)
-        if password:
-            user.set_password(password)
-        else:
-            user.set_unusable_password()
-        user.save()
-        return user
-
-    def create_superuser(self, username, email, password):
-        u = self.create_user(username, email, password)
-        u.is_staff = True
-        u.is_active = True
-        u.is_superuser = True
-        u.save()
-
-    def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
-        "Generates a random password with the given length and given allowed_chars"
-        # Note that default value of allowed_chars does not have "I" or letters
-        # that look like it -- just to avoid confusion.
-        from random import choice
-        return ''.join([choice(allowed_chars) for i in range(length)])
-
-class User(models.Model):
-    """Users within the Django authentication system are represented by this model.
-
-    Username and password are required. Other fields are optional.
-    """
-    username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."))
-    first_name = models.CharField(_('first name'), max_length=30, blank=True)
-    last_name = models.CharField(_('last name'), max_length=30, blank=True)
-    email = models.EmailField(_('e-mail address'), blank=True)
-    password = models.CharField(_('password'), max_length=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
-    is_staff = models.BooleanField(_('staff status'), default=False, help_text=_("Designates whether the user can log into this admin site."))
-    is_active = models.BooleanField(_('active'), default=True, help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."))
-    is_superuser = models.BooleanField(_('superuser status'), default=False, help_text=_("Designates that this user has all permissions without explicitly assigning them."))
-    last_login = models.DateTimeField(_('last login'), default=datetime.datetime.now)
-    date_joined = models.DateTimeField(_('date joined'), default=datetime.datetime.now)
-    groups = models.ManyToManyField(Group, verbose_name=_('groups'), blank=True,
-        help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."))
-    user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True)
-    objects = UserManager()
+class UserTemplate(models.Model):
+    """ Base class from which all User models inherit.  """
 
     class Meta:
+        abstract = True
+        app_label = "auth"
         verbose_name = _('user')
         verbose_name_plural = _('users')
 
-    def __unicode__(self):
-        return self.username
-
-    def get_absolute_url(self):
-        return "/users/%s/" % urllib.quote(smart_str(self.username))
-
     def is_anonymous(self):
         "Always returns False. This is a way of comparing User objects to anonymous users."
         return False
@@ -167,41 +121,6 @@ class User(models.Model):
         """
         return True
 
-    def get_full_name(self):
-        "Returns the first_name plus the last_name, with a space in between."
-        full_name = u'%s %s' % (self.first_name, self.last_name)
-        return full_name.strip()
-
-    def set_password(self, raw_password):
-        import random
-        algo = 'sha1'
-        salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
-        hsh = get_hexdigest(algo, salt, raw_password)
-        self.password = '%s$%s$%s' % (algo, salt, hsh)
-
-    def check_password(self, raw_password):
-        """
-        Returns a boolean of whether the raw_password was correct. Handles
-        encryption formats behind the scenes.
-        """
-        # Backwards-compatibility check. Older passwords won't include the
-        # algorithm or salt.
-        if '$' not in self.password:
-            is_correct = (self.password == get_hexdigest('md5', '', raw_password))
-            if is_correct:
-                # Convert the password to the new, more secure format.
-                self.set_password(raw_password)
-                self.save()
-            return is_correct
-        return check_password(raw_password, self.password)
-
-    def set_unusable_password(self):
-        # Sets a value that will never be a valid hash
-        self.password = UNUSABLE_PASSWORD
-
-    def has_usable_password(self):
-        return self.password != UNUSABLE_PASSWORD
-
     def get_group_permissions(self):
         """
         Returns a list of permission strings that this user has through
@@ -273,11 +192,6 @@ class User(models.Model):
             m.delete()
         return messages
 
-    def email_user(self, subject, message, from_email=None):
-        "Sends an e-mail to this User."
-        from django.core.mail import send_mail
-        send_mail(subject, message, from_email, [self.email])
-
     def get_profile(self):
         """
         Returns site-specific profile for this user. Raises
@@ -295,6 +209,23 @@ class User(models.Model):
                 raise SiteProfileNotAvailable
         return self._profile_cache
 
+# Implement the User model according to settings
+from django.db.models import import_model
+User = import_model('auth',settings.AUTH_USER_MODULE)
+
+# Add the User model to the django models cache 
+# These two lines allow the custom auth_user model to play nicely with syncdb 
+# and other systems that rely on functions like 
+# django.db.models.loading.get_model(...) 
+#from django.db.models.loading import cache 
+#cache.register_models('auth', User)
+
+# We need to remove whatever we used as the AUTH_USER_MODUlE from the
+# db cache so apps like contenttypes don't think that it's part
+# of contrib.auth
+#if settings.AUTH_USER_MODULE != global_settings.AUTH_USER_MODULE:
+#    del cache.app_models['auth'][auth_user_module_parts[1].lower()]
+
 class Message(models.Model):
     """
     The message system is a lightweight way to queue messages for given
diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py
index 2071710..d7b32ef 100644
--- a/django/contrib/auth/tests/basic.py
+++ b/django/contrib/auth/tests/basic.py
@@ -1,5 +1,11 @@
 
 BASIC_TESTS = """
+>>> from django.contrib.auth.models import User
+>>> User._meta.app_label
+'auth'
+>>> User._meta.object_name
+'User'
+
 >>> from django.contrib.auth.models import User, AnonymousUser
 >>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
 >>> u.has_usable_password()
diff --git a/django/contrib/auth/user_model.py b/django/contrib/auth/user_model.py
new file mode 100644
index 0000000..76b821b
--- /dev/null
+++ b/django/contrib/auth/user_model.py
@@ -0,0 +1,104 @@
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+from django.contrib.auth.models import Group, Permission, UserTemplate, get_hexdigest, check_password, UNUSABLE_PASSWORD
+
+import datetime, urllib
+
+
+class UserManager(models.Manager):
+    def create_user(self, username, email, password=None):
+        "Creates and saves a User with the given username, e-mail and password."
+        now = datetime.datetime.now()
+        user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now)
+        if password:
+            user.set_password(password)
+        else:
+            user.set_unusable_password()
+        user.save()
+        return user
+
+    def create_superuser(self, username, email, password):
+        u = self.create_user(username, email, password)
+        u.is_staff = True
+        u.is_active = True
+        u.is_superuser = True
+        u.save()
+        return u
+
+    def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
+        "Generates a random password with the given length and given allowed_chars"
+        # Note that default value of allowed_chars does not have "I" or letters
+        # that look like it -- just to avoid confusion.
+        from random import choice
+        return ''.join([choice(allowed_chars) for i in range(length)])
+
+
+class User(UserTemplate):
+    """Users within the Django authentication system are represented by this model.
+
+    Username and password are required. Other fields are optional.
+    """
+    username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."))
+    first_name = models.CharField(_('first name'), max_length=30, blank=True)
+    last_name = models.CharField(_('last name'), max_length=30, blank=True)
+    email = models.EmailField(_('e-mail address'), blank=True)
+    password = models.CharField(_('password'), max_length=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
+    is_staff = models.BooleanField(_('staff status'), default=False, help_text=_("Designates whether the user can log into this admin site."))
+    is_active = models.BooleanField(_('active'), default=True, help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."))
+    is_superuser = models.BooleanField(_('superuser status'), default=False, help_text=_("Designates that this user has all permissions without explicitly assigning them."))
+    last_login = models.DateTimeField(_('last login'), default=datetime.datetime.now)
+    date_joined = models.DateTimeField(_('date joined'), default=datetime.datetime.now)
+    groups = models.ManyToManyField(Group, verbose_name=_('groups'), blank=True,
+        help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."))
+    user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True)
+    objects = UserManager()
+    
+    class Meta(UserTemplate.Meta):
+        db_table = "auth_user"
+
+    def __unicode__(self):
+        return self.username
+
+    def get_absolute_url(self):
+        return "/users/%s/" % urllib.quote(smart_str(self.username))
+
+    def get_full_name(self):
+        "Returns the first_name plus the last_name, with a space in between."
+        full_name = u'%s %s' % (self.first_name, self.last_name)
+        return full_name.strip()
+
+    def set_password(self, raw_password):
+        import random
+        algo = 'sha1'
+        salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
+        hsh = get_hexdigest(algo, salt, raw_password)
+        self.password = '%s$%s$%s' % (algo, salt, hsh)
+
+    def check_password(self, raw_password):
+        """
+        Returns a boolean of whether the raw_password was correct. Handles
+        encryption formats behind the scenes.
+        """
+        # Backwards-compatibility check. Older passwords won't include the
+        # algorithm or salt.
+        if '$' not in self.password:
+            is_correct = (self.password == get_hexdigest('md5', '', raw_password))
+            if is_correct:
+                # Convert the password to the new, more secure format.
+                self.set_password(raw_password)
+                self.save()
+            return is_correct
+        return check_password(raw_password, self.password)
+
+    def set_unusable_password(self):
+        # Sets a value that will never be a valid hash
+        self.password = UNUSABLE_PASSWORD
+
+    def has_usable_password(self):
+        return self.password != UNUSABLE_PASSWORD
+
+    def email_user(self, subject, message, from_email=None):
+        "Sends an e-mail to this User."
+        from django.core.mail import send_mail
+        send_mail(subject, message, from_email, [self.email])
\ No newline at end of file
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index 5413133..9a39cb1 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -1,7 +1,7 @@
 from django.conf import settings
 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
 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, import_model
 from django.db.models.query import Q
 from django.db.models.manager import Manager
 from django.db.models.base import Model
diff --git a/django/db/models/loading.py b/django/db/models/loading.py
index 6837e07..bcb5d26 100644
--- a/django/db/models/loading.py
+++ b/django/db/models/loading.py
@@ -8,7 +8,7 @@ import sys
 import os
 import threading
 
-__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
+__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models', 'import_model',
         'load_app', 'app_cache_ready')
 
 class AppCache(object):

@@ -174,6 +182,25 @@ class AppCache(object):
                 if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
                     continue
             model_dict[model_name] = model
+            
+    def import_model(self, app_label,import_name):
+        """
+        Import a model for an app.
+        This allows you to making a default model which can be overridden in a custom module.
+        
+        import_name The absolute import name for the model being imported
+        returns a model
+        """
+        if not import_name:
+            raise ImportError, "Have no model to import to the '%s' app" % app_label
+        # parts[0] = module path 
+        # parts[1] = class name 
+        parts = import_name.rsplit('.', 1) 
+        module = __import__(parts[0], {}, {}, [parts[0]]) 
+        if not hasattr(module, parts[1]):
+            dir(module)
+            raise ImportError, "AppCache could not import model, %s" % import_name
+        return getattr(module, parts[1]) 
 
 cache = AppCache()
 
@@ -185,5 +212,6 @@ get_app_errors = cache.get_app_errors
 get_models = cache.get_models
 get_model = cache.get_model
 register_models = cache.register_models
+import_model = cache.import_model
 load_app = cache.load_app
 app_cache_ready = cache.app_cache_ready
