diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html
index 3d2a07eba2..7bbd73a464 100644
--- a/django/contrib/admin/templates/admin/base.html
+++ b/django/contrib/admin/templates/admin/base.html
@@ -26,7 +26,7 @@
{% if user.is_active and user.is_staff %}
{% trans 'Welcome,' %}
-
{% filter force_escape %}{% firstof user.get_short_name user.username %}{% endfilter %}.
+
{% filter force_escape %}{% firstof user.get_short_name user.get_username %}{% endfilter %}.
{% block userlinks %}
{% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %}
diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html
index 55dd4a3b4c..870c4648a6 100644
--- a/django/contrib/admin/templates/admin/object_history.html
+++ b/django/contrib/admin/templates/admin/object_history.html
@@ -29,7 +29,7 @@
{% for action in action_list %}
{{ action.action_time|date:"DATETIME_FORMAT" }} |
- {{ action.user.username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %} |
+ {{ action.user.get_username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %} |
{{ action.change_message }} |
{% endfor %}
diff --git a/django/contrib/admin/templates/registration/password_reset_email.html b/django/contrib/admin/templates/registration/password_reset_email.html
index 0eef4a7f9d..a220f12033 100644
--- a/django/contrib/admin/templates/registration/password_reset_email.html
+++ b/django/contrib/admin/templates/registration/password_reset_email.html
@@ -5,7 +5,7 @@
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
{% endblock %}
-{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
+{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% trans "Thanks for using our site!" %}
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 5c08b0615f..7fc723f475 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -11,14 +11,13 @@ from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.utils.html import escape
from django.utils.decorators import method_decorator
-from django.utils.safestring import mark_safe
-from django.utils import six
from django.utils.translation import ugettext, ugettext_lazy as _
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
csrf_protect_m = method_decorator(csrf_protect)
+
class GroupAdmin(admin.ModelAdmin):
search_fields = ('name',)
ordering = ('name',)
@@ -106,9 +105,10 @@ class UserAdmin(admin.ModelAdmin):
raise PermissionDenied
if extra_context is None:
extra_context = {}
+ username_field = self.model._meta.get_field(self.model.USERNAME_FIELD)
defaults = {
'auto_populated_fields': (),
- 'username_help_text': self.model._meta.get_field('username').help_text,
+ 'username_help_text': username_field.help_text,
}
extra_context.update(defaults)
return super(UserAdmin, self).add_view(request, form_url,
@@ -171,4 +171,3 @@ class UserAdmin(admin.ModelAdmin):
admin.site.register(Group, GroupAdmin)
admin.site.register(User, UserAdmin)
-
diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
index 00cb67a0b5..db99c94838 100644
--- a/django/contrib/auth/backends.py
+++ b/django/contrib/auth/backends.py
@@ -105,7 +105,7 @@ class RemoteUserBackend(ModelBackend):
# built-in safeguards for multiple threads.
if self.create_unknown_user:
user, created = UserModel.objects.get_or_create(**{
- getattr(UserModel, 'USERNAME_FIELD', 'username'): username
+ UserModel.USERNAME_FIELD: username
})
if created:
user = self.configure_user(user)
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index c114c18afe..fbd8d0482e 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -52,6 +52,9 @@ class ReadOnlyPasswordHashField(forms.Field):
kwargs.setdefault("required", False)
super(ReadOnlyPasswordHashField, self).__init__(*args, **kwargs)
+ def clean_password(self):
+ return self.initial
+
class UserCreationForm(forms.ModelForm):
"""
@@ -118,9 +121,6 @@ class UserChangeForm(forms.ModelForm):
"this user's password, but you can change the password "
"using
this form."))
- def clean_password(self):
- return self.initial["password"]
-
class Meta:
model = User
@@ -160,7 +160,7 @@ class AuthenticationForm(forms.Form):
# Set the label for the "username" field.
UserModel = get_user_model()
- username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
+ username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
self.fields['username'].label = capfirst(username_field.verbose_name)
def clean(self):
diff --git a/django/contrib/auth/management/commands/changepassword.py b/django/contrib/auth/management/commands/changepassword.py
index 1a2387442c..ff38836a95 100644
--- a/django/contrib/auth/management/commands/changepassword.py
+++ b/django/contrib/auth/management/commands/changepassword.py
@@ -34,7 +34,7 @@ class Command(BaseCommand):
try:
u = UserModel.objects.using(options.get('database')).get(**{
- getattr(UserModel, 'USERNAME_FIELD', 'username'): username
+ UserModel.USERNAME_FIELD: username
})
except UserModel.DoesNotExist:
raise CommandError("user '%s' does not exist" % username)
diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py
index 8130b326c5..cb5d906342 100644
--- a/django/contrib/auth/management/commands/createsuperuser.py
+++ b/django/contrib/auth/management/commands/createsuperuser.py
@@ -42,7 +42,7 @@ class Command(BaseCommand):
UserModel = get_user_model()
- username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
+ username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
other_fields = UserModel.REQUIRED_FIELDS
# If not provided, create the user with an unusable password
@@ -74,7 +74,7 @@ class Command(BaseCommand):
# Get a username
while username is None:
- username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
+ username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
if not username:
input_msg = capfirst(username_field.verbose_name)
if default_username:
@@ -91,7 +91,7 @@ class Command(BaseCommand):
continue
try:
UserModel.objects.using(database).get(**{
- getattr(UserModel, 'USERNAME_FIELD', 'username'): username
+ UserModel.USERNAME_FIELD: username
})
except UserModel.DoesNotExist:
pass
diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py
index df616a9243..0398cfaf1e 100644
--- a/django/contrib/auth/middleware.py
+++ b/django/contrib/auth/middleware.py
@@ -55,7 +55,7 @@ class RemoteUserMiddleware(object):
# getting passed in the headers, then the correct user is already
# persisted in the session and we don't need to continue.
if request.user.is_authenticated():
- if request.user.username == self.clean_username(username, request):
+ if request.user.get_username() == self.clean_username(username, request):
return
# We are seeing this user for the first time in this session, attempt
# to authenticate the user.
@@ -75,6 +75,6 @@ class RemoteUserMiddleware(object):
backend = auth.load_backend(backend_str)
try:
username = backend.clean_username(username)
- except AttributeError: # Backend has no clean_username method.
+ except AttributeError: # Backend has no clean_username method.
pass
return username
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index abcc7ceafc..bd7bf4a162 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -165,7 +165,7 @@ class BaseUserManager(models.Manager):
return get_random_string(length, allowed_chars)
def get_by_natural_key(self, username):
- return self.get(**{getattr(self.model, 'USERNAME_FIELD', 'username'): username})
+ return self.get(**{self.model.USERNAME_FIELD: username})
class UserManager(BaseUserManager):
@@ -227,6 +227,7 @@ def _user_has_module_perms(user, app_label):
return False
+@python_2_unicode_compatible
class AbstractBaseUser(models.Model):
password = models.CharField(_('password'), max_length=128)
last_login = models.DateTimeField(_('last login'), default=timezone.now)
@@ -236,6 +237,16 @@ class AbstractBaseUser(models.Model):
class Meta:
abstract = True
+ def get_username(self):
+ "Return the identifying username for this User"
+ return getattr(self, self.USERNAME_FIELD)
+
+ def __str__(self):
+ return self.get_username()
+
+ def natural_key(self):
+ return (self.get_username(),)
+
def is_anonymous(self):
"""
Always returns False. This is a way of comparing User objects to
@@ -277,7 +288,6 @@ class AbstractBaseUser(models.Model):
raise NotImplementedError()
-@python_2_unicode_compatible
class AbstractUser(AbstractBaseUser):
"""
An abstract base class implementing a fully featured User model with
@@ -314,6 +324,7 @@ class AbstractUser(AbstractBaseUser):
objects = UserManager()
+ USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
@@ -321,12 +332,6 @@ class AbstractUser(AbstractBaseUser):
verbose_name_plural = _('users')
abstract = True
- def __str__(self):
- return self.username
-
- def natural_key(self):
- return (self.username,)
-
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
diff --git a/django/contrib/comments/admin.py b/django/contrib/comments/admin.py
index 0024a1d1b5..a651baaadf 100644
--- a/django/contrib/comments/admin.py
+++ b/django/contrib/comments/admin.py
@@ -1,11 +1,22 @@
from __future__ import unicode_literals
from django.contrib import admin
+from django.contrib.auth import get_user_model
from django.contrib.comments.models import Comment
from django.utils.translation import ugettext_lazy as _, ungettext
from django.contrib.comments import get_model
from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete
+
+class UsernameSearch(object):
+ """The User object may not be auth.User, so we need to provide
+ a mechanism for issuing the equivalent of a .filter(user__username=...)
+ search in CommentAdmin.
+ """
+ def __str__(self):
+ return 'user__%s' % get_user_model().USERNAME_FIELD
+
+
class CommentsAdmin(admin.ModelAdmin):
fieldsets = (
(None,
@@ -24,7 +35,7 @@ class CommentsAdmin(admin.ModelAdmin):
date_hierarchy = 'submit_date'
ordering = ('-submit_date',)
raw_id_fields = ('user',)
- search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address')
+ search_fields = ('comment', UsernameSearch(), 'user_name', 'user_email', 'user_url', 'ip_address')
actions = ["flag_comments", "approve_comments", "remove_comments"]
def get_actions(self, request):
diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py
index a39c2622dd..c263ea7d10 100644
--- a/django/contrib/comments/models.py
+++ b/django/contrib/comments/models.py
@@ -19,14 +19,14 @@ class BaseCommentAbstractModel(models.Model):
"""
# Content-object field
- content_type = models.ForeignKey(ContentType,
+ content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s")
- object_pk = models.TextField(_('object ID'))
+ object_pk = models.TextField(_('object ID'))
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
# Metadata about the comment
- site = models.ForeignKey(Site)
+ site = models.ForeignKey(Site)
class Meta:
abstract = True
@@ -50,21 +50,21 @@ class Comment(BaseCommentAbstractModel):
# Who posted this comment? If ``user`` is set then it was an authenticated
# user; otherwise at least user_name should have been set and the comment
# was posted by a non-authenticated user.
- user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
blank=True, null=True, related_name="%(class)s_comments")
- user_name = models.CharField(_("user's name"), max_length=50, blank=True)
- user_email = models.EmailField(_("user's email address"), blank=True)
- user_url = models.URLField(_("user's URL"), blank=True)
+ user_name = models.CharField(_("user's name"), max_length=50, blank=True)
+ user_email = models.EmailField(_("user's email address"), blank=True)
+ user_url = models.URLField(_("user's URL"), blank=True)
comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)
# Metadata about the comment
submit_date = models.DateTimeField(_('date/time submitted'), default=None)
- ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
- is_public = models.BooleanField(_('is public'), default=True,
+ ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
+ is_public = models.BooleanField(_('is public'), default=True,
help_text=_('Uncheck this box to make the comment effectively ' \
'disappear from the site.'))
- is_removed = models.BooleanField(_('is removed'), default=False,
+ is_removed = models.BooleanField(_('is removed'), default=False,
help_text=_('Check this box if the comment is inappropriate. ' \
'A "This comment has been removed" message will ' \
'be displayed instead.'))
@@ -96,9 +96,9 @@ class Comment(BaseCommentAbstractModel):
"""
if not hasattr(self, "_userinfo"):
userinfo = {
- "name" : self.user_name,
- "email" : self.user_email,
- "url" : self.user_url
+ "name": self.user_name,
+ "email": self.user_email,
+ "url": self.user_url
}
if self.user_id:
u = self.user
@@ -111,7 +111,7 @@ class Comment(BaseCommentAbstractModel):
if u.get_full_name():
userinfo["name"] = self.user.get_full_name()
elif not self.user_name:
- userinfo["name"] = u.username
+ userinfo["name"] = u.get_username()
self._userinfo = userinfo
return self._userinfo
userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__)
@@ -174,9 +174,9 @@ class CommentFlag(models.Model):
design users are only allowed to flag a comment with a given flag once;
if you want rating look elsewhere.
"""
- user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), related_name="comment_flags")
- comment = models.ForeignKey(Comment, verbose_name=_('comment'), related_name="flags")
- flag = models.CharField(_('flag'), max_length=30, db_index=True)
+ user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), related_name="comment_flags")
+ comment = models.ForeignKey(Comment, verbose_name=_('comment'), related_name="flags")
+ flag = models.CharField(_('flag'), max_length=30, db_index=True)
flag_date = models.DateTimeField(_('date'), default=None)
# Constants for flag types
@@ -192,7 +192,7 @@ class CommentFlag(models.Model):
def __str__(self):
return "%s flag of comment ID %s by %s" % \
- (self.flag, self.comment_id, self.user.username)
+ (self.flag, self.comment_id, self.user.get_username())
def save(self, *args, **kwargs):
if self.flag_date is None:
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index c9a11606b3..27d5a48ac6 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -15,7 +15,6 @@ from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST
-
class CommentPostBadRequest(http.HttpResponseBadRequest):
"""
Response returned when a comment post is invalid. If ``DEBUG`` is on a
@@ -27,6 +26,7 @@ class CommentPostBadRequest(http.HttpResponseBadRequest):
if settings.DEBUG:
self.content = render_to_string("comments/400-debug.html", {"why": why})
+
@csrf_protect
@require_POST
def post_comment(request, next=None, using=None):
@@ -40,7 +40,7 @@ def post_comment(request, next=None, using=None):
data = request.POST.copy()
if request.user.is_authenticated():
if not data.get('name', ''):
- data["name"] = request.user.get_full_name() or request.user.username
+ data["name"] = request.user.get_full_name() or request.user.get_username()
if not data.get('email', ''):
data["email"] = request.user.email
@@ -98,8 +98,8 @@ def post_comment(request, next=None, using=None):
]
return render_to_response(
template_list, {
- "comment" : form.data.get("comment", ""),
- "form" : form,
+ "comment": form.data.get("comment", ""),
+ "form": form,
"next": next,
},
RequestContext(request, {})
@@ -113,9 +113,9 @@ def post_comment(request, next=None, using=None):
# Signal that the comment is about to be saved
responses = signals.comment_will_be_posted.send(
- sender = comment.__class__,
- comment = comment,
- request = request
+ sender=comment.__class__,
+ comment=comment,
+ request=request
)
for (receiver, response) in responses:
@@ -126,15 +126,14 @@ def post_comment(request, next=None, using=None):
# Save the comment and signal that it was saved
comment.save()
signals.comment_was_posted.send(
- sender = comment.__class__,
- comment = comment,
- request = request
+ sender=comment.__class__,
+ comment=comment,
+ request=request
)
return next_redirect(data, next, comment_done, c=comment._get_pk_val())
comment_done = confirmation_view(
- template = "comments/posted.html",
- doc = """Display a "comment was posted" success page."""
+ template="comments/posted.html",
+ doc="""Display a "comment was posted" success page."""
)
-
diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
index bbe6d6ec33..fd2e56ebeb 100644
--- a/docs/topics/auth.txt
+++ b/docs/topics/auth.txt
@@ -149,6 +149,12 @@ Methods
:class:`~django.contrib.auth.models.User` objects have the following custom
methods:
+ .. method:: models.User.get_username()
+
+ Returns the username for the user. Since the User model can be swapped
+ out, you should use this method instead of referencing the username
+ attribute directly.
+
.. method:: models.User.is_anonymous()
Always returns ``False``. This is a way of differentiating
@@ -1826,11 +1832,12 @@ different User model.
Instead of referring to :class:`~django.contrib.auth.models.User` directly,
you should reference the user model using
:func:`django.contrib.auth.get_user_model()`. This method will return the
-currently active User model -- the custom User model if one is specified, or
+currently active User model -- the custom User model if one is specified, or
:class:`~django.contrib.auth.User` otherwise.
-In relations to the User model, you should specify the custom model using
-the :setting:`AUTH_USER_MODEL` setting. For example::
+When you define a foreign key or many-to-many relations to the User model,
+you should specify the custom model using the :setting:`AUTH_USER_MODEL`
+setting. For example::
from django.conf import settings
from django.db import models
@@ -1910,6 +1917,60 @@ password resets. You must then provide some key implementation details:
identifies the user in an informal way. It may also return the same
value as :meth:`django.contrib.auth.User.get_full_name()`.
+The following methods are available on any subclass of
+:class:`~django.contrib.auth.models.AbstractBaseUser`::
+
+.. class:: models.AbstractBaseUser
+
+ .. method:: models.AbstractBaseUser.get_username()
+
+ Returns the value of the field nominated by ``USERNAME_FIELD``.
+
+ .. method:: models.AbstractBaseUser.is_anonymous()
+
+ Always returns ``False``. This is a way of differentiating
+ from :class:`~django.contrib.auth.models.AnonymousUser` objects.
+ Generally, you should prefer using
+ :meth:`~django.contrib.auth.models.AbstractBaseUser.is_authenticated()` to this
+ method.
+
+ .. method:: models.AbstractBaseUser.is_authenticated()
+
+ Always returns ``True``. This is a way to tell if the user has been
+ authenticated. This does not imply any permissions, and doesn't check
+ if the user is active - it only indicates that the user has provided a
+ valid username and password.
+
+ .. method:: models.AbstractBaseUser.set_password(raw_password)
+
+ Sets the user's password to the given raw string, taking care of the
+ password hashing. Doesn't save the
+ :class:`~django.contrib.auth.models.AbstractBaseUser` object.
+
+ .. method:: models.AbstractBaseUser.check_password(raw_password)
+
+ Returns ``True`` if the given raw string is the correct password for
+ the user. (This takes care of the password hashing in making the
+ comparison.)
+
+ .. method:: models.AbstractBaseUser.set_unusable_password()
+
+ Marks the user as having no password set. This isn't the same as
+ having a blank string for a password.
+ :meth:`~django.contrib.auth.models.AbstractBaseUser.check_password()` for this user
+ will never return ``True``. Doesn't save the
+ :class:`~django.contrib.auth.models.AbstractBaseUser` object.
+
+ You may need this if authentication for your application takes place
+ against an existing external source such as an LDAP directory.
+
+ .. method:: models.AbstractBaseUser.has_usable_password()
+
+ Returns ``False`` if
+ :meth:`~django.contrib.auth.models.AbstractBaseUser.set_unusable_password()` has
+ been called for this user.
+
+
You should also define a custom manager for your User model. If your User
model defines `username` and `email` fields the same as Django's default User,
you can just install Django's
@@ -1941,6 +2002,31 @@ additional methods:
Unlike `create_user()`, `create_superuser()` *must* require the caller
to provider a password.
+:class:`~django.contrib.auth.models.BaseUserManager` provides the following
+utility methods:
+
+.. class:: models.BaseUserManager
+ .. method:: models.BaseUserManager.normalize_email(email)
+
+ A classmethod that normalizes email addresses by lowercasing
+ the domain portion of the email address.
+
+ .. method:: models.BaseUserManager.get_by_natural_key(username)
+
+ Retrieves a user instance using the contents of the field
+ nominated by ``USERNAME_FIELD``.
+
+ .. method:: models.BaseUserManager.make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')
+
+ Returns a random password with the given length and given string of
+ allowed characters. (Note that the default value of ``allowed_chars``
+ doesn't contain letters that can cause user confusion, including:
+
+ * ``i``, ``l``, ``I``, and ``1`` (lowercase letter i, lowercase
+ letter L, uppercase letter i, and the number one)
+ * ``o``, ``O``, and ``0`` (uppercase letter o, lowercase letter o,
+ and zero)
+
Extending Django's default User
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2020,6 +2106,16 @@ control access of the User to admin content:
Returns True if the user has permission to access models in
the given app.
+You will also need to register your custom User model with the admin. If
+your custom User model extends :class:`~django.contrib.auth.models.AbstractUser`,
+you can use Django's existing :class:`~django.contrib.auth.admin.UserAdmin`
+class. However, if your User model extends
+:class:`~django.contrib.auth.models.AbstractBaseUser`, you'll need to define
+a custom ModelAdmin class. It may be possible to subclass the default
+:class:`~django.contrib.auth.admin.UserAdmin`; however, you'll need to
+override any of the definitions that refer to fields on
+:class:`~django.contrib.auth.models.AbstractUser` that aren't on your
+custom User class.
Custom users and Proxy models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2036,11 +2132,11 @@ behavior into your User subclass.
A full example
--------------
-Here is an example of a full models.py for an admin-compliant custom
-user app. This user model uses an email address as the username, and has a
-required date of birth; it provides no permission checking, beyond a simple
-`admin` flag on the user account. This model would be compatible with all
-the built-in auth forms and views, except for the User creation forms.
+Here is an example of an admin-compliant custom user app. This user model uses
+an email address as the username, and has a required date of birth; it
+provides no permission checking, beyond a simple `admin` flag on the user
+account. This model would be compatible with all the built-in auth forms and
+views, except for the User creation forms.
This code would all live in a ``models.py`` file for a custom
authentication app::
@@ -2086,7 +2182,9 @@ authentication app::
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
- max_length=255
+ max_length=255,
+ unique=True,
+ db_index=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
@@ -2124,6 +2222,87 @@ authentication app::
# Simplest possible answer: All admins are staff
return self.is_admin
+Then, to register this custom User model with Django's admin, the following
+code would be required in ``admin.py``::
+
+ from django import forms
+ from django.contrib import admin
+ from django.contrib.auth.models import Group
+ from django.contrib.auth.admin import UserAdmin
+ from django.contrib.auth.forms import ReadOnlyPasswordHashField
+
+ from customauth.models import MyUser
+
+
+ class UserCreationForm(forms.ModelForm):
+ """A form for creating new users. Includes all the required
+ fields, plus a repeated password."""
+ password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
+ password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
+
+ class Meta:
+ model = MyUser
+ fields = ('email', 'date_of_birth')
+
+ def clean_password2(self):
+ # Check that the two password entries match
+ password1 = self.cleaned_data.get("password1")
+ password2 = self.cleaned_data.get("password2")
+ if password1 and password2 and password1 != password2:
+ raise forms.ValidationError('Passwords don't match')
+ return password2
+
+ def save(self, commit=True):
+ # Save the provided password in hashed format
+ user = super(UserCreationForm, self).save(commit=False)
+ user.set_password(self.cleaned_data["password1"])
+ if commit:
+ user.save()
+ return user
+
+
+ class UserChangeForm(forms.ModelForm):
+ """A form for updateing users. Includes all the fields on
+ the user, but replaces the password field with admin's
+ pasword hash display field.
+ """
+ password = ReadOnlyPasswordHashField()
+
+ class Meta:
+ model = MyUser
+
+
+ class MyUserAdmin(UserAdmin):
+ # The forms to add and change user instances
+ form = UserChangeForm
+ add_form = UserCreationForm
+
+ # The fields to be used in displaying the User model.
+ # These override the definitions on the base UserAdmin
+ # that reference specific fields on auth.User.
+ list_display = ('email', 'date_of_birth', 'is_admin')
+ list_filter = ('is_admin',)
+ fieldsets = (
+ (None, {'fields': ('email', 'password')}),
+ ('Personal info', {'fields': ('date_of_birth',)}),
+ ('Permissions', {'fields': ('is_admin',)}),
+ ('Important dates', {'fields': ('last_login',)}),
+ )
+ add_fieldsets = (
+ (None, {
+ 'classes': ('wide',),
+ 'fields': ('email', 'date_of_birth', 'password1', 'password2')}
+ ),
+ )
+ search_fields = ('email',)
+ ordering = ('email',)
+ filter_horizontal = ()
+
+ # Now register the new UserAdmin...
+ admin.site.register(MyUser, MyUserAdmin)
+ # ... and, since we're not using Django's builtin permissions,
+ # unregister the Group model from admin.
+ admin.site.unregister(Group)
.. _authentication-backends: