Modified auth management commands to handle custom user definitions.

This commit is contained in:
Russell Keith-Magee 2012-06-04 20:10:51 +08:00
parent 7cc0baf89d
commit dabe362836
13 changed files with 357 additions and 186 deletions

View File

@ -4,6 +4,7 @@ from django.contrib.admin.forms import AdminAuthenticationForm
from django.contrib.auth.views import login from django.contrib.auth.views import login
from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth import REDIRECT_FIELD_NAME
def staff_member_required(view_func): def staff_member_required(view_func):
""" """
Decorator for views that checks that the user is logged in and is a staff Decorator for views that checks that the user is logged in and is a staff

View File

@ -64,16 +64,16 @@ class UserCreationForm(forms.ModelForm):
} }
username = forms.RegexField(label=_("Username"), max_length=30, username = forms.RegexField(label=_("Username"), max_length=30,
regex=r'^[\w.@+-]+$', regex=r'^[\w.@+-]+$',
help_text = _("Required. 30 characters or fewer. Letters, digits and " help_text=_("Required. 30 characters or fewer. Letters, digits and "
"@/./+/-/_ only."), "@/./+/-/_ only."),
error_messages = { error_messages={
'invalid': _("This value may contain only letters, numbers and " 'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")}) "@/./+/-/_ characters.")})
password1 = forms.CharField(label=_("Password"), password1 = forms.CharField(label=_("Password"),
widget=forms.PasswordInput) widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), password2 = forms.CharField(label=_("Password confirmation"),
widget=forms.PasswordInput, widget=forms.PasswordInput,
help_text = _("Enter the same password as above, for verification.")) help_text=_("Enter the same password as above, for verification."))
class Meta: class Meta:
model = User model = User
@ -108,9 +108,9 @@ class UserCreationForm(forms.ModelForm):
class UserChangeForm(forms.ModelForm): class UserChangeForm(forms.ModelForm):
username = forms.RegexField( username = forms.RegexField(
label=_("Username"), max_length=30, regex=r"^[\w.@+-]+$", label=_("Username"), max_length=30, regex=r"^[\w.@+-]+$",
help_text = _("Required. 30 characters or fewer. Letters, digits and " help_text=_("Required. 30 characters or fewer. Letters, digits and "
"@/./+/-/_ only."), "@/./+/-/_ only."),
error_messages = { error_messages={
'invalid': _("This value may contain only letters, numbers and " 'invalid': _("This value may contain only letters, numbers and "
"@/./+/-/_ characters.")}) "@/./+/-/_ characters.")})
password = ReadOnlyPasswordHashField(label=_("Password"), password = ReadOnlyPasswordHashField(label=_("Password"),

View File

@ -4,9 +4,11 @@ Creates permissions for all installed apps that need permissions.
import getpass import getpass
import locale import locale
import unicodedata import unicodedata
from django.contrib.auth import models as auth_app from django.contrib.auth import models as auth_app
from django.core import exceptions
from django.db.models import get_models, signals from django.db.models import get_models, signals
from django.contrib.auth.models import User from django.contrib.auth.models import User, get_user_model
def _get_permission_codename(action, opts): def _get_permission_codename(action, opts):
@ -60,7 +62,9 @@ def create_permissions(app, created_models, verbosity, **kwargs):
def create_superuser(app, created_models, verbosity, db, **kwargs): def create_superuser(app, created_models, verbosity, db, **kwargs):
from django.core.management import call_command from django.core.management import call_command
if auth_app.User in created_models and kwargs.get('interactive', True): UserModel = get_user_model()
if UserModel in created_models and kwargs.get('interactive', True):
msg = ("\nYou just installed Django's auth system, which means you " msg = ("\nYou just installed Django's auth system, which means you "
"don't have any superusers defined.\nWould you like to create one " "don't have any superusers defined.\nWould you like to create one "
"now? (yes/no): ") "now? (yes/no): ")
@ -100,16 +104,24 @@ def get_default_username(check_db=True):
:returns: The username, or an empty string if no username can be :returns: The username, or an empty string if no username can be
determined. determined.
""" """
from django.contrib.auth.management.commands.createsuperuser import ( # If the User model has been swapped out, we can't make any assumptions
RE_VALID_USERNAME) # about the default user name.
if User._meta.swapped:
return ''
default_username = get_system_username() default_username = get_system_username()
try: try:
default_username = unicodedata.normalize('NFKD', default_username)\ default_username = unicodedata.normalize('NFKD', default_username)\
.encode('ascii', 'ignore').replace(' ', '').lower() .encode('ascii', 'ignore').replace(' ', '').lower()
except UnicodeDecodeError: except UnicodeDecodeError:
return '' return ''
if not RE_VALID_USERNAME.match(default_username):
# Run the username validator
try:
User._meta.get_field('username').run_validators(default_username)
except exceptions.ValidationError:
return '' return ''
# Don't return the default username if it is already taken. # Don't return the default username if it is already taken.
if check_db and default_username: if check_db and default_username:
try: try:
@ -120,8 +132,7 @@ def get_default_username(check_db=True):
return '' return ''
return default_username return default_username
signals.post_syncdb.connect(create_permissions, signals.post_syncdb.connect(create_permissions,
dispatch_uid = "django.contrib.auth.management.create_permissions") dispatch_uid="django.contrib.auth.management.create_permissions")
signals.post_syncdb.connect(create_superuser, signals.post_syncdb.connect(create_superuser,
sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser") sender=auth_app, dispatch_uid="django.contrib.auth.management.create_superuser")

View File

@ -2,7 +2,7 @@ import getpass
from optparse import make_option from optparse import make_option
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import User from django.contrib.auth.models import get_user_model
from django.db import DEFAULT_DB_ALIAS from django.db import DEFAULT_DB_ALIAS
@ -30,12 +30,16 @@ class Command(BaseCommand):
else: else:
username = getpass.getuser() username = getpass.getuser()
UserModel = get_user_model()
try: try:
u = User.objects.using(options.get('database')).get(username=username) u = UserModel.objects.using(options.get('database')).get(**{
except User.DoesNotExist: getattr(UserModel, 'USERNAME_FIELD', 'username'): username
})
except UserModel.DoesNotExist:
raise CommandError("user '%s' does not exist" % username) raise CommandError("user '%s' does not exist" % username)
self.stdout.write("Changing password for user '%s'\n" % u.username) self.stdout.write("Changing password for user '%s'\n" % u)
MAX_TRIES = 3 MAX_TRIES = 3
count = 0 count = 0
@ -48,9 +52,9 @@ class Command(BaseCommand):
count = count + 1 count = count + 1
if count == MAX_TRIES: if count == MAX_TRIES:
raise CommandError("Aborting password change for user '%s' after %s attempts" % (username, count)) raise CommandError("Aborting password change for user '%s' after %s attempts" % (u, count))
u.set_password(p1) u.set_password(p1)
u.save() u.save()
return "Password changed successfully for user '%s'" % u.username return "Password changed successfully for user '%s'" % u

View File

@ -3,108 +3,113 @@ Management utility to create superusers.
""" """
import getpass import getpass
import re
import sys import sys
from optparse import make_option from optparse import make_option
from django.contrib.auth.models import User
from django.contrib.auth.management import get_default_username from django.contrib.auth.management import get_default_username
from django.contrib.auth.models import get_user_model
from django.core import exceptions from django.core import exceptions
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS from django.db import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext as _ from django.utils.text import capfirst
RE_VALID_USERNAME = re.compile('[\w.@+-]+$')
EMAIL_RE = re.compile(
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
def is_valid_email(value):
if not EMAIL_RE.search(value):
raise exceptions.ValidationError(_('Enter a valid e-mail address.'))
class Command(BaseCommand): class Command(BaseCommand):
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--username', dest='username', default=None, make_option('--username', dest='username', default=None,
help='Specifies the username for the superuser.'), help='Specifies the username for the superuser.'),
make_option('--email', dest='email', default=None,
help='Specifies the email address for the superuser.'),
make_option('--noinput', action='store_false', dest='interactive', default=True, make_option('--noinput', action='store_false', dest='interactive', default=True,
help=('Tells Django to NOT prompt the user for input of any kind. ' help=('Tells Django to NOT prompt the user for input of any kind. '
'You must use --username and --email with --noinput, and ' 'You must use --username with --noinput, along with an option for '
'superusers created with --noinput will not be able to log ' 'any other required field. Superusers created with --noinput will '
'in until they\'re given a valid password.')), ' not be able to log in until they\'re given a valid password.')),
make_option('--database', action='store', dest='database', make_option('--database', action='store', dest='database',
default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'), default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'),
) + tuple(
make_option('--%s' % field, dest=field, default=None,
help='Specifies the %s for the superuser.' % field)
for field in getattr(get_user_model(), 'REQUIRED_FIELDS', ['email'])
) )
help = 'Used to create a superuser.' help = 'Used to create a superuser.'
def handle(self, *args, **options): def handle(self, *args, **options):
username = options.get('username', None) username = options.get('username', None)
email = options.get('email', None)
interactive = options.get('interactive') interactive = options.get('interactive')
verbosity = int(options.get('verbosity', 1)) verbosity = int(options.get('verbosity', 1))
database = options.get('database') database = options.get('database')
# Do quick and dirty validation if --noinput UserModel = get_user_model()
if not interactive:
if not username or not email: username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
raise CommandError("You must use --username and --email with --noinput.") other_fields = getattr(UserModel, 'REQUIRED_FIELDS', ['email'])
if not RE_VALID_USERNAME.match(username):
raise CommandError("Invalid username. Use only letters, digits, and underscores")
try:
is_valid_email(email)
except exceptions.ValidationError:
raise CommandError("Invalid email address.")
# If not provided, create the user with an unusable password # If not provided, create the user with an unusable password
password = None password = None
other_data = {}
# Prompt for username/email/password. Enclose this whole thing in a # Do quick and dirty validation if --noinput
# try/except to trap for a keyboard interrupt and exit gracefully. if not interactive:
if interactive: try:
if not username:
raise CommandError("You must use --username with --noinput.")
username = username_field.clean(username, None)
for field_name in other_fields:
if options.get(field_name):
field = UserModel._meta.get_field(field_name)
other_data[field_name] = field.clean(options[field_name], None)
else:
raise CommandError("You must use --%s with --noinput." % field_name)
except exceptions.ValidationError, e:
raise CommandError('; '.join(e.messages))
else:
# Prompt for username/password, and any other required fields.
# Enclose this whole thing in a try/except to trap for a
# keyboard interrupt and exit gracefully.
default_username = get_default_username() default_username = get_default_username()
try: try:
# Get a username # Get a username
while 1: while username is None:
username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
if not username: if not username:
input_msg = 'Username' input_msg = capfirst(username_field.verbose_name)
if default_username: if default_username:
input_msg += ' (leave blank to use %r)' % default_username input_msg += ' (leave blank to use %r)' % default_username
username = raw_input(input_msg + ': ') raw_value = raw_input(input_msg + ': ')
if default_username and username == '': if default_username and raw_value == '':
username = default_username username = default_username
if not RE_VALID_USERNAME.match(username): try:
self.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.") username = username_field.clean(raw_value, None)
except exceptions.ValidationError, e:
self.stderr.write("Error: %s" % '; '.join(e.messages))
username = None username = None
continue continue
try: try:
User.objects.using(database).get(username=username) UserModel.objects.using(database).get(**{
except User.DoesNotExist: getattr(UserModel, 'USERNAME_FIELD', 'username'): username
break })
except UserModel.DoesNotExist:
pass
else: else:
self.stderr.write("Error: That username is already taken.") self.stderr.write("Error: That username is already taken.")
username = None username = None
# Get an email for field_name in other_fields:
while 1: field = UserModel._meta.get_field(field_name)
if not email: other_data[field_name] = None
email = raw_input('E-mail address: ') while other_data[field_name] is None:
try: raw_value = raw_input(capfirst(field.verbose_name + ': '))
is_valid_email(email) try:
except exceptions.ValidationError: other_data[field_name] = field.clean(raw_value, None)
self.stderr.write("Error: That e-mail address is invalid.") except exceptions.ValidationError, e:
email = None self.stderr.write("Error: %s" % '; '.join(e.messages))
else: other_data[field_name] = None
break
# Get a password # Get a password
while 1: while password is None:
if not password: if not password:
password = getpass.getpass() password = getpass.getpass()
password2 = getpass.getpass('Password (again): ') password2 = getpass.getpass('Password (again): ')
@ -116,12 +121,11 @@ class Command(BaseCommand):
self.stderr.write("Error: Blank passwords aren't allowed.") self.stderr.write("Error: Blank passwords aren't allowed.")
password = None password = None
continue continue
break
except KeyboardInterrupt: except KeyboardInterrupt:
self.stderr.write("\nOperation cancelled.") self.stderr.write("\nOperation cancelled.")
sys.exit(1) sys.exit(1)
User.objects.db_manager(database).create_superuser(username, email, password) UserModel.objects.db_manager(database).create_superuser(username=username, password=password, **other_data)
if verbosity >= 1: if verbosity >= 1:
self.stdout.write("Superuser created successfully.") self.stdout.write("Superuser created successfully.")

View File

@ -1,7 +1,10 @@
import re
import urllib import urllib
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core import validators
from django.db import models from django.db import models
from django.db.models.manager import EmptyManager from django.db.models.manager import EmptyManager
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
@ -128,7 +131,7 @@ class Group(models.Model):
return (self.name,) return (self.name,)
class UserManager(models.Manager): class BaseUserManager(models.Manager):
@classmethod @classmethod
def normalize_email(cls, email): def normalize_email(cls, email):
@ -145,6 +148,24 @@ class UserManager(models.Manager):
email = '@'.join([email_name, domain_part.lower()]) email = '@'.join([email_name, domain_part.lower()])
return email return email
def make_random_password(self, length=10,
allowed_chars='abcdefghjkmnpqrstuvwxyz'
'ABCDEFGHJKLMNPQRSTUVWXYZ'
'23456789'):
"""
Generates a random password with the given length and given
allowed_chars. Note that the default value of allowed_chars does not
have "I" or "O" or letters and digits that look similar -- just to
avoid confusion.
"""
return get_random_string(length, allowed_chars)
def get_by_natural_key(self, username):
return self.get(**{getattr(self, 'USERNAME_FIELD', 'username'): username})
class UserManager(BaseUserManager):
def create_user(self, username, email=None, password=None): def create_user(self, username, email=None, password=None):
""" """
Creates and saves a User with the given username, email and password. Creates and saves a User with the given username, email and password.
@ -169,21 +190,6 @@ class UserManager(models.Manager):
u.save(using=self._db) u.save(using=self._db)
return u return u
def make_random_password(self, length=10,
allowed_chars='abcdefghjkmnpqrstuvwxyz'
'ABCDEFGHJKLMNPQRSTUVWXYZ'
'23456789'):
"""
Generates a random password with the given length and given
allowed_chars. Note that the default value of allowed_chars does not
have "I" or "O" or letters and digits that look similar -- just to
avoid confusion.
"""
return get_random_string(length, allowed_chars)
def get_by_natural_key(self, username):
return self.get(username=username)
# A few helper functions for common logic between User and AnonymousUser. # A few helper functions for common logic between User and AnonymousUser.
def _user_get_all_permissions(user, obj): def _user_get_all_permissions(user, obj):
@ -219,6 +225,7 @@ def _user_has_module_perms(user, app_label):
class AbstractBaseUser(models.Model): class AbstractBaseUser(models.Model):
password = models.CharField(_('password'), max_length=128) password = models.CharField(_('password'), max_length=128)
last_login = models.DateTimeField(_('last login'), default=timezone.now)
class Meta: class Meta:
abstract = True abstract = True
@ -264,6 +271,12 @@ class AbstractBaseUser(models.Model):
raise NotImplementedError() raise NotImplementedError()
def get_user_model():
"Return the User model that is active in this project"
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
return models.get_model(app_label, model_name)
class User(AbstractBaseUser): class User(AbstractBaseUser):
""" """
Users within the Django authentication system are represented by this Users within the Django authentication system are represented by this
@ -273,10 +286,13 @@ class User(AbstractBaseUser):
""" """
username = models.CharField(_('username'), max_length=30, unique=True, username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and ' help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters')) '@/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\w.@+-]+$'), _(u'Enter a valid username.'), 'invalid')
])
first_name = models.CharField(_('first name'), max_length=30, blank=True) first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last 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) email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False, is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin ' help_text=_('Designates whether the user can log into this admin '
'site.')) 'site.'))
@ -286,7 +302,6 @@ class User(AbstractBaseUser):
is_superuser = models.BooleanField(_('superuser status'), default=False, is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without ' help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.')) 'explicitly assigning them.'))
last_login = models.DateTimeField(_('last login'), default=timezone.now)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now) date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
groups = models.ManyToManyField(Group, verbose_name=_('groups'), groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will ' blank=True, help_text=_('The groups this user belongs to. A user will '
@ -295,6 +310,7 @@ class User(AbstractBaseUser):
user_permissions = models.ManyToManyField(Permission, user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True, verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.') help_text='Specific permissions for this user.')
objects = UserManager() objects = UserManager()
class Meta: class Meta:
@ -318,6 +334,10 @@ class User(AbstractBaseUser):
full_name = u'%s %s' % (self.first_name, self.last_name) full_name = u'%s %s' % (self.first_name, self.last_name)
return full_name.strip() return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def get_group_permissions(self, obj=None): def get_group_permissions(self, obj=None):
""" """
Returns a list of permission strings that this user has through his/her Returns a list of permission strings that this user has through his/her

View File

@ -1,26 +1,15 @@
from django.contrib.auth.tests.auth_backends import (BackendTest, from django.contrib.auth.tests.custom_user import *
RowlevelBackendTest, AnonymousUserBackendTest, NoBackendsTest, from django.contrib.auth.tests.auth_backends import *
InActiveUserBackendTest) from django.contrib.auth.tests.basic import *
from django.contrib.auth.tests.basic import BasicTestCase from django.contrib.auth.tests.context_processors import *
from django.contrib.auth.tests.context_processors import AuthContextProcessorTests from django.contrib.auth.tests.decorators import *
from django.contrib.auth.tests.decorators import LoginRequiredTestCase from django.contrib.auth.tests.forms import *
from django.contrib.auth.tests.forms import (UserCreationFormTest, from django.contrib.auth.tests.remote_user import *
AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, from django.contrib.auth.tests.management import *
UserChangeFormTest, PasswordResetFormTest) from django.contrib.auth.tests.models import *
from django.contrib.auth.tests.remote_user import (RemoteUserTest, from django.contrib.auth.tests.hashers import *
RemoteUserNoCreateTest, RemoteUserCustomTest) from django.contrib.auth.tests.signals import *
from django.contrib.auth.tests.management import ( from django.contrib.auth.tests.tokens import *
GetDefaultUsernameTestCase, from django.contrib.auth.tests.views import *
ChangepasswordManagementCommandTestCase,
)
from django.contrib.auth.tests.models import (ProfileTestCase, NaturalKeysTestCase,
LoadDataWithoutNaturalKeysTestCase, LoadDataWithNaturalKeysTestCase,
UserManagerTestCase)
from django.contrib.auth.tests.hashers import TestUtilsHashPass
from django.contrib.auth.tests.signals import SignalTestCase
from django.contrib.auth.tests.tokens import TokenGeneratorTest
from django.contrib.auth.tests.views import (AuthViewNamedURLTests,
PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest,
LoginURLSettings)
# The password for the fixture data users is 'password' # The password for the fixture data users is 'password'

View File

@ -1,14 +1,8 @@
from django.test import TestCase from django.test import TestCase
from django.utils.unittest import skipUnless
from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.models import User, AnonymousUser
from django.core.management import call_command from django.core.management import call_command
from StringIO import StringIO from StringIO import StringIO
try:
import crypt as crypt_module
except ImportError:
crypt_module = None
class BasicTestCase(TestCase): class BasicTestCase(TestCase):
def test_user(self): def test_user(self):
@ -35,7 +29,7 @@ class BasicTestCase(TestCase):
self.assertFalse(u.is_superuser) self.assertFalse(u.is_superuser)
# Check API-based user creation with no password # Check API-based user creation with no password
u2 = User.objects.create_user('testuser2', 'test2@example.com') User.objects.create_user('testuser2', 'test2@example.com')
self.assertFalse(u.has_usable_password()) self.assertFalse(u.has_usable_password())
def test_user_no_email(self): def test_user_no_email(self):
@ -66,48 +60,3 @@ class BasicTestCase(TestCase):
self.assertTrue(super.is_superuser) self.assertTrue(super.is_superuser)
self.assertTrue(super.is_active) self.assertTrue(super.is_active)
self.assertTrue(super.is_staff) self.assertTrue(super.is_staff)
def test_createsuperuser_management_command(self):
"Check the operation of the createsuperuser management command"
# We can use the management command to create a superuser
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe",
email="joe@somewhere.org",
stdout=new_io
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, 'Superuser created successfully.')
u = User.objects.get(username="joe")
self.assertEqual(u.email, 'joe@somewhere.org')
# created password should be unusable
self.assertFalse(u.has_usable_password())
# We can supress output on the management command
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe2",
email="joe2@somewhere.org",
verbosity=0,
stdout=new_io
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, '')
u = User.objects.get(username="joe2")
self.assertEqual(u.email, 'joe2@somewhere.org')
self.assertFalse(u.has_usable_password())
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe+admin@somewhere.org",
email="joe@somewhere.org",
stdout=new_io
)
u = User.objects.get(username="joe+admin@somewhere.org")
self.assertEqual(u.email, 'joe@somewhere.org')
self.assertFalse(u.has_usable_password())

View File

@ -0,0 +1,78 @@
# The custom User uses email as the unique identifier, and requires
# that every user provide a date of birth. This lets us test
# changes in username datatype, and non-text required fields.
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
class CustomUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=CustomUserManager.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, password, date_of_birth):
u = self.create_user(username, password=password, date_of_birth=date_of_birth)
u.is_admin = True
u.save(using=self._db)
return u
class CustomUser(AbstractBaseUser):
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
is_admin = models.BooleanField(default=False)
date_of_birth = models.DateField()
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['date_of_birth']
class Meta:
app_label = 'auth'
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __unicode__(self):
return self.email
# Maybe required?
def get_group_permissions(self, obj=None):
return set()
def get_all_permissions(self, obj=None):
return set()
def has_perm(self, perm, obj=None):
return True
def has_perms(self, perm_list, obj=None):
return True
def has_module_perms(self, app_label):
return True
# Admin required fields
@property
def is_staff(self):
return self.is_admin
@property
def is_active(self):
return True

View File

@ -1,9 +1,14 @@
from datetime import date
from StringIO import StringIO from StringIO import StringIO
from django.contrib.auth import models, management from django.contrib.auth import models, management
from django.contrib.auth.management.commands import changepassword from django.contrib.auth.management.commands import changepassword
from django.contrib.auth.models import User
from django.contrib.auth.tests import CustomUser
from django.core.management import call_command
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings
class GetDefaultUsernameTestCase(TestCase): class GetDefaultUsernameTestCase(TestCase):
@ -43,7 +48,7 @@ class ChangepasswordManagementCommandTestCase(TestCase):
self.stderr.close() self.stderr.close()
def test_that_changepassword_command_changes_joes_password(self): def test_that_changepassword_command_changes_joes_password(self):
" Executing the changepassword management command should change joe's password " "Executing the changepassword management command should change joe's password"
self.assertTrue(self.user.check_password('qwerty')) self.assertTrue(self.user.check_password('qwerty'))
command = changepassword.Command() command = changepassword.Command()
command._get_pass = lambda *args: 'not qwerty' command._get_pass = lambda *args: 'not qwerty'
@ -64,3 +69,92 @@ class ChangepasswordManagementCommandTestCase(TestCase):
with self.assertRaises(CommandError): with self.assertRaises(CommandError):
command.execute("joe", stdout=self.stdout, stderr=self.stderr) command.execute("joe", stdout=self.stdout, stderr=self.stderr)
class CreatesuperuserManagementCommandTestCase(TestCase):
def test_createsuperuser(self):
"Check the operation of the createsuperuser management command"
# We can use the management command to create a superuser
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe",
email="joe@somewhere.org",
stdout=new_io
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, 'Superuser created successfully.')
u = User.objects.get(username="joe")
self.assertEqual(u.email, 'joe@somewhere.org')
# created password should be unusable
self.assertFalse(u.has_usable_password())
def test_verbosity_zero(self):
# We can supress output on the management command
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe2",
email="joe2@somewhere.org",
verbosity=0,
stdout=new_io
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, '')
u = User.objects.get(username="joe2")
self.assertEqual(u.email, 'joe2@somewhere.org')
self.assertFalse(u.has_usable_password())
def test_email_in_username(self):
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe+admin@somewhere.org",
email="joe@somewhere.org",
stdout=new_io
)
u = User.objects.get(username="joe+admin@somewhere.org")
self.assertEqual(u.email, 'joe@somewhere.org')
self.assertFalse(u.has_usable_password())
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
def test_swappable_user(self):
"A superuser can be created when a custom User model is in use"
# We can use the management command to create a superuser
# We skip validation because the temporary substitution of the
# swappable User model messes with validation.
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe@somewhere.org",
date_of_birth="1976-04-01",
stdout=new_io,
skip_validation=True
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, 'Superuser created successfully.')
u = CustomUser.objects.get(email="joe@somewhere.org")
self.assertEqual(u.date_of_birth, date(1976, 4, 1))
# created password should be unusable
self.assertFalse(u.has_usable_password())
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
def test_swappable_user_missing_required_field(self):
"A superuser can be created when a custom User model is in use"
# We can use the management command to create a superuser
# We skip validation because the temporary substitution of the
# swappable User model messes with validation.
new_io = StringIO()
with self.assertRaises(CommandError):
call_command("createsuperuser",
interactive=False,
username="joe@somewhere.org",
stdout=new_io,
stderr=new_io,
skip_validation=True
)
self.assertEqual(CustomUser.objects.count(), 0)

View File

@ -3,6 +3,7 @@ from django.conf import settings
from django.utils.http import int_to_base36, base36_to_int from django.utils.http import int_to_base36, base36_to_int
from django.utils.crypto import constant_time_compare, salted_hmac from django.utils.crypto import constant_time_compare, salted_hmac
class PasswordResetTokenGenerator(object): class PasswordResetTokenGenerator(object):
""" """
Strategy object used to generate and check tokens for the password Strategy object used to generate and check tokens for the password

View File

@ -3,42 +3,54 @@ Global Django exception and warning classes.
""" """
from functools import reduce from functools import reduce
class DjangoRuntimeWarning(RuntimeWarning): class DjangoRuntimeWarning(RuntimeWarning):
pass pass
class ObjectDoesNotExist(Exception): class ObjectDoesNotExist(Exception):
"The requested object does not exist" "The requested object does not exist"
silent_variable_failure = True silent_variable_failure = True
class MultipleObjectsReturned(Exception): class MultipleObjectsReturned(Exception):
"The query returned multiple objects when only one was expected." "The query returned multiple objects when only one was expected."
pass pass
class SuspiciousOperation(Exception): class SuspiciousOperation(Exception):
"The user did something suspicious" "The user did something suspicious"
pass pass
class PermissionDenied(Exception): class PermissionDenied(Exception):
"The user did not have permission to do that" "The user did not have permission to do that"
pass pass
class ViewDoesNotExist(Exception): class ViewDoesNotExist(Exception):
"The requested view does not exist" "The requested view does not exist"
pass pass
class MiddlewareNotUsed(Exception): class MiddlewareNotUsed(Exception):
"This middleware is not used in this server configuration" "This middleware is not used in this server configuration"
pass pass
class ImproperlyConfigured(Exception): class ImproperlyConfigured(Exception):
"Django is somehow improperly configured" "Django is somehow improperly configured"
pass pass
class FieldError(Exception): class FieldError(Exception):
"""Some kind of problem with a model field.""" """Some kind of problem with a model field."""
pass pass
NON_FIELD_ERRORS = '__all__' NON_FIELD_ERRORS = '__all__'
class ValidationError(Exception): class ValidationError(Exception):
"""An error while validating data.""" """An error while validating data."""
def __init__(self, message, code=None, params=None): def __init__(self, message, code=None, params=None):
@ -85,4 +97,3 @@ class ValidationError(Exception):
else: else:
error_dict[NON_FIELD_ERRORS] = self.messages error_dict[NON_FIELD_ERRORS] = self.messages
return error_dict return error_dict

View File

@ -9,6 +9,7 @@ from django.utils.ipv6 import is_valid_ipv6_address
# These values, if given to validate(), will trigger the self.required check. # These values, if given to validate(), will trigger the self.required check.
EMPTY_VALUES = (None, '', [], (), {}) EMPTY_VALUES = (None, '', [], (), {})
class RegexValidator(object): class RegexValidator(object):
regex = '' regex = ''
message = _(u'Enter a valid value.') message = _(u'Enter a valid value.')
@ -33,13 +34,14 @@ class RegexValidator(object):
if not self.regex.search(smart_unicode(value)): if not self.regex.search(smart_unicode(value)):
raise ValidationError(self.message, code=self.code) raise ValidationError(self.message, code=self.code)
class URLValidator(RegexValidator): class URLValidator(RegexValidator):
regex = re.compile( regex = re.compile(
r'^(?:http|ftp)s?://' # http:// or https:// r'^(?:http|ftp)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain... r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
r'localhost|' #localhost... r'localhost|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE) r'(?:/?|[/?]\S+)$', re.IGNORECASE)
def __call__(self, value): def __call__(self, value):
@ -51,8 +53,8 @@ class URLValidator(RegexValidator):
value = smart_unicode(value) value = smart_unicode(value)
scheme, netloc, path, query, fragment = urlparse.urlsplit(value) scheme, netloc, path, query, fragment = urlparse.urlsplit(value)
try: try:
netloc = netloc.encode('idna') # IDN -> ACE netloc = netloc.encode('idna') # IDN -> ACE
except UnicodeError: # invalid domain part except UnicodeError: # invalid domain part
raise e raise e
url = urlparse.urlunsplit((scheme, netloc, path, query, fragment)) url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
super(URLValidator, self).__call__(url) super(URLValidator, self).__call__(url)
@ -68,6 +70,7 @@ def validate_integer(value):
except (ValueError, TypeError): except (ValueError, TypeError):
raise ValidationError('') raise ValidationError('')
class EmailValidator(RegexValidator): class EmailValidator(RegexValidator):
def __call__(self, value): def __call__(self, value):
@ -99,10 +102,12 @@ validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of l
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid') validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
def validate_ipv6_address(value): def validate_ipv6_address(value):
if not is_valid_ipv6_address(value): if not is_valid_ipv6_address(value):
raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid') raise ValidationError(_(u'Enter a valid IPv6 address.'), code='invalid')
def validate_ipv46_address(value): def validate_ipv46_address(value):
try: try:
validate_ipv4_address(value) validate_ipv4_address(value)
@ -118,6 +123,7 @@ ip_address_validator_map = {
'ipv6': ([validate_ipv6_address], _('Enter a valid IPv6 address.')), 'ipv6': ([validate_ipv6_address], _('Enter a valid IPv6 address.')),
} }
def ip_address_validators(protocol, unpack_ipv4): def ip_address_validators(protocol, unpack_ipv4):
""" """
Depending on the given parameters returns the appropriate validators for Depending on the given parameters returns the appropriate validators for
@ -140,7 +146,7 @@ validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_
class BaseValidator(object): class BaseValidator(object):
compare = lambda self, a, b: a is not b compare = lambda self, a, b: a is not b
clean = lambda self, x: x clean = lambda self, x: x
message = _(u'Ensure this value is %(limit_value)s (it is %(show_value)s).') message = _(u'Ensure this value is %(limit_value)s (it is %(show_value)s).')
code = 'limit_value' code = 'limit_value'
@ -157,25 +163,28 @@ class BaseValidator(object):
params=params, params=params,
) )
class MaxValueValidator(BaseValidator): class MaxValueValidator(BaseValidator):
compare = lambda self, a, b: a > b compare = lambda self, a, b: a > b
message = _(u'Ensure this value is less than or equal to %(limit_value)s.') message = _(u'Ensure this value is less than or equal to %(limit_value)s.')
code = 'max_value' code = 'max_value'
class MinValueValidator(BaseValidator): class MinValueValidator(BaseValidator):
compare = lambda self, a, b: a < b compare = lambda self, a, b: a < b
message = _(u'Ensure this value is greater than or equal to %(limit_value)s.') message = _(u'Ensure this value is greater than or equal to %(limit_value)s.')
code = 'min_value' code = 'min_value'
class MinLengthValidator(BaseValidator): class MinLengthValidator(BaseValidator):
compare = lambda self, a, b: a < b compare = lambda self, a, b: a < b
clean = lambda self, x: len(x) clean = lambda self, x: len(x)
message = _(u'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).') message = _(u'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).')
code = 'min_length' code = 'min_length'
class MaxLengthValidator(BaseValidator): class MaxLengthValidator(BaseValidator):
compare = lambda self, a, b: a > b compare = lambda self, a, b: a > b
clean = lambda self, x: len(x) clean = lambda self, x: len(x)
message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).')
code = 'max_length' code = 'max_length'