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: