Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #19077, #19079 -- Made USERNAME_FIELD a required field, and mod…

…ified UserAdmin to match.
  • Loading branch information...
commit c433fcb3fb34fccd69782979f0e7cd5f2d4a4893 1 parent 5fb2232
@freakboy3742 freakboy3742 authored
View
2  django/contrib/admin/templates/admin/base.html
@@ -26,7 +26,7 @@
{% if user.is_active and user.is_staff %}
<div id="user-tools">
{% trans 'Welcome,' %}
- <strong>{% filter force_escape %}{% firstof user.get_short_name user.username %}{% endfilter %}</strong>.
+ <strong>{% filter force_escape %}{% firstof user.get_short_name user.get_username %}{% endfilter %}</strong>.
{% block userlinks %}
{% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %}
View
2  django/contrib/admin/templates/admin/object_history.html
@@ -29,7 +29,7 @@
{% for action in action_list %}
<tr>
<th scope="row">{{ action.action_time|date:"DATETIME_FORMAT" }}</th>
- <td>{{ action.user.username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %}</td>
+ <td>{{ action.user.get_username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %}</td>
<td>{{ action.change_message }}</td>
</tr>
{% endfor %}
View
2  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!" %}
View
7 django/contrib/auth/admin.py
@@ -11,14 +11,13 @@
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 @@ def add_view(self, request, form_url='', extra_context=None):
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 @@ def response_add(self, request, obj, post_url_continue='../%s/'):
admin.site.register(Group, GroupAdmin)
admin.site.register(User, UserAdmin)
-
View
2  django/contrib/auth/backends.py
@@ -105,7 +105,7 @@ def authenticate(self, remote_user):
# 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)
View
8 django/contrib/auth/forms.py
@@ -52,6 +52,9 @@ def __init__(self, *args, **kwargs):
kwargs.setdefault("required", False)
super(ReadOnlyPasswordHashField, self).__init__(*args, **kwargs)
+ def clean_password(self):
@ptone Collaborator
ptone added a note

see: https://code.djangoproject.com/ticket/19133

There are a couple things overlooked here - first the method should be def clean(self, value) - but even with that, the value that is passed from the readonly widget is None - so we need to get the password hash from the UserChangeForm's initial data as was being done before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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 <a href=\"password/\">this form</a>."))
- def clean_password(self):
- return self.initial["password"]
-
class Meta:
model = User
@@ -160,7 +160,7 @@ def __init__(self, request=None, *args, **kwargs):
# 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):
View
2  django/contrib/auth/management/commands/changepassword.py
@@ -34,7 +34,7 @@ def handle(self, *args, **options):
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)
View
6 django/contrib/auth/management/commands/createsuperuser.py
@@ -42,7 +42,7 @@ def handle(self, *args, **options):
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 @@ def handle(self, *args, **options):
# 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 @@ def handle(self, *args, **options):
continue
try:
UserModel.objects.using(database).get(**{
- getattr(UserModel, 'USERNAME_FIELD', 'username'): username
+ UserModel.USERNAME_FIELD: username
})
except UserModel.DoesNotExist:
pass
View
4 django/contrib/auth/middleware.py
@@ -55,7 +55,7 @@ def process_request(self, request):
# 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 @@ def clean_username(self, username, request):
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
View
21 django/contrib/auth/models.py
@@ -165,7 +165,7 @@ def make_random_password(self, length=10,
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 @@ def get_short_name(self):
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 Meta:
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)
View
13 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):
View
36 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 @@ def _get_userinfo(self):
"""
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 @@ def _get_userinfo(self):
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 Meta:
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:
View
25 django/contrib/comments/views/comments.py
@@ -15,7 +15,6 @@
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 @@ def __init__(self, why):
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."""
)
-
View
197 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:
@ptone

see: https://code.djangoproject.com/ticket/19133

There are a couple things overlooked here - first the method should be def clean(self, value) - but even with that, the value that is passed from the readonly widget is None - so we need to get the password hash from the UserChangeForm's initial data as was being done before.

Please sign in to comment.
Something went wrong with that request. Please try again.