Fixed #3011 -- Added swappable auth.User models.

Thanks to the many people that contributed to the development and review of
this patch, including (but not limited to) Jacob Kaplan-Moss, Anssi
Kääriäinen, Ramiro Morales, Preston Holmes, Josh Ourisman, Thomas Sutton,
and Roger Barnes, as well as the many, many people who have contributed to
the design discussion around this ticket over many years.

Squashed commit of the following:

commit d84749a0f0
Merge: 531e771 7c11b1a
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Wed Sep 26 18:37:04 2012 +0800

    Merge remote-tracking branch 'django/master' into t3011

commit 531e7715da
Merge: 29d1abb 1f84b04
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Wed Sep 26 07:09:23 2012 +0800

    Merged recent trunk changes.

commit 29d1abbe35
Merge: 8a527dd 54c81a1
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Sep 24 07:49:46 2012 +0800

    Merge remote-tracking branch 'django/master' into t3011

commit 8a527dda13
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Sep 24 07:48:05 2012 +0800

    Ensure sequences are reset correctly in the presence of swapped models.

commit e2b6e22f29
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 23 17:53:05 2012 +0800

    Modifications to the handling and docs for auth forms.

commit 98aba856b5
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 23 15:28:57 2012 +0800

    Improved error handling and docs for get_user_model()

commit 0229209c84
Merge: 6494bf9 8599f64
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 23 14:50:11 2012 +0800

    Merged recent Django trunk changes.

commit 6494bf91f2
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Sep 17 21:38:44 2012 +0800

    Improved validation of swappable model settings.

commit 5a04cde342
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Sep 17 07:15:14 2012 +0800

    Removed some unused imports.

commit ffd535e413
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 20:31:28 2012 +0800

    Corrected attribute access on for get_by_natural_key

commit 913e1ac84c
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 20:12:34 2012 +0800

    Added test for proxy model safeguards on swappable models.

commit 280bf19e94
Merge: dbb3900 935a863
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 18:16:49 2012 +0800

    Merge remote-tracking branch 'django/master' into t3011

commit dbb3900775
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 18:09:27 2012 +0800

    Fixes for Python 3 compatibility.

commit dfd72131d8
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 15:54:30 2012 +0800

    Added protection against proxying swapped models.

commit abcb027190
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 15:11:10 2012 +0800

    Cleanup and documentation of AbstractUser base class.

commit a9491a8776
Merge: fd8bb4e 08bcb4a
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 14:46:49 2012 +0800

    Merge commit '08bcb4aec1ed154cefc631b8510ee13e9af0c19d' into t3011

commit fd8bb4e3e4
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 14:20:14 2012 +0800

    Documentation improvements coming from community review.

commit b550a6d06d
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 13:52:47 2012 +0800

    Refactored skipIfCustomUser into the contrib.auth tests.

commit 52a02f1110
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 13:46:10 2012 +0800

    Refactored common 'get' pattern into manager method.

commit b441a6bbc7
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 16 13:41:33 2012 +0800

    Added note about backwards incompatible change to admin login messages.

commit 08bcb4aec1
Author: Anssi Kääriäinen <akaariai@gmail.com>
Date:   Sat Sep 15 18:30:33 2012 +0300

    Splitted User to AbstractUser and User

commit d9f5e5addb
Author: Anssi Kääriäinen <akaariai@gmail.com>
Date:   Sat Sep 15 18:30:02 2012 +0300

    Reworked REQUIRED_FIELDS + create_user() interaction

commit 579f152e4a
Merge: 9184972 93e6733
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sat Sep 15 20:18:37 2012 +0800

    Merge remote-tracking branch 'django/master' into t3011

commit 918497218c
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sat Sep 15 20:18:19 2012 +0800

    Deprecate AUTH_PROFILE_MODULE and get_profile().

commit 334cdfc1bb
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sat Sep 15 20:00:12 2012 +0800

    Added release notes for new swappable User feature.

commit 5d7bb22e8d
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sat Sep 15 19:59:49 2012 +0800

    Ensure swapped models can't be queried.

commit 57ac6e3d32
Merge: f2ec915 abfba3b
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sat Sep 15 14:31:54 2012 +0800

    Merge remote-tracking branch 'django/master' into t3011

commit f2ec915b20
Merge: 1952656 5e99a3d
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 9 08:29:51 2012 +0800

    Merge remote-tracking branch 'django/master' into t3011

commit 19526563b5
Merge: 2c5e833 c4aa26a
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 9 08:22:26 2012 +0800

    Merge recent changes from master.

commit 2c5e833a30
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 9 07:53:46 2012 +0800

    Corrected admin_views tests following removal of the email fallback on admin logins.

commit 20d1892491
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sun Sep 9 01:00:37 2012 +0800

    Added conditional skips for all tests dependent on the default User model

commit 40ea8b8882
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sat Sep 8 23:47:02 2012 +0800

    Added documentation for REQUIRED_FIELDS in custom auth.

commit e6aaf65970
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Sat Sep 8 23:20:02 2012 +0800

    Added first draft of custom User docs.

    Thanks to Greg Turner for the initial text.

commit 75118bd242
Author: Thomas Sutton <me@thomas-sutton.id.au>
Date:   Mon Aug 20 11:17:26 2012 +0800

    Admin app should not allow username discovery

    The admin app login form should not allow users to discover the username
    associated with an email address.

commit d088b3af58
Author: Thomas Sutton <me@thomas-sutton.id.au>
Date:   Mon Aug 20 10:32:13 2012 +0800

    Admin app login form should use swapped user model

commit 7e82e83d67
Merge: e29c010 39aa890
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Fri Sep 7 23:45:03 2012 +0800

    Merged master changes.

commit e29c010beb
Merge: 8e3fd70 30bdf22
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Aug 20 13:12:57 2012 +0800

    Merge remote-tracking branch 'django/master' into t3011

commit 8e3fd703d0
Merge: 507bb50 26e0ba0
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Aug 20 13:09:09 2012 +0800

    Merged recent changes from trunk.

commit 507bb50a92
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Jun 4 20:41:37 2012 +0800

    Modified auth app so that login with alternate auth app is possible.

commit dabe362836
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Jun 4 20:10:51 2012 +0800

    Modified auth management commands to handle custom user definitions.

commit 7cc0baf89d
Author: Russell Keith-Magee <russell@keith-magee.com>
Date:   Mon Jun 4 14:17:28 2012 +0800

    Added model Meta option for swappable models, and made auth.User a swappable model
This commit is contained in:
Russell Keith-Magee 2012-09-26 18:48:09 +08:00
parent 7c11b1a470
commit 70a0de37d1
57 changed files with 1426 additions and 357 deletions

View File

@ -488,6 +488,8 @@ PROFANITIES_LIST = ()
# AUTHENTICATION #
##################
AUTH_USER_MODEL = 'auth.User'
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
LOGIN_URL = '/accounts/login/'

View File

@ -4,12 +4,12 @@ from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy, ugettext as _
from django.utils.translation import ugettext_lazy
ERROR_MESSAGE = ugettext_lazy("Please enter the correct username and password "
"for a staff account. Note that both fields are case-sensitive.")
class AdminAuthenticationForm(AuthenticationForm):
"""
A custom authentication form used in the admin app.
@ -26,17 +26,6 @@ class AdminAuthenticationForm(AuthenticationForm):
if username and password:
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
if '@' in username:
# Mistakenly entered e-mail address instead of username? Look it up.
try:
user = User.objects.get(email=username)
except (User.DoesNotExist, User.MultipleObjectsReturned):
# Nothing to do here, moving along.
pass
else:
if user.check_password(password):
message = _("Your e-mail address is not your username."
" Try '%s' instead.") % user.username
raise forms.ValidationError(message)
elif not self.user_cache.is_active or not self.user_cache.is_staff:
raise forms.ValidationError(message)

View File

@ -1,8 +1,8 @@
from __future__ import unicode_literals
from django.db import models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.contrib.admin.util import quote
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text
@ -12,15 +12,17 @@ ADDITION = 1
CHANGE = 2
DELETION = 3
class LogEntryManager(models.Manager):
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
e = self.model(None, None, user_id, content_type_id, smart_text(object_id), object_repr[:200], action_flag, change_message)
e.save()
@python_2_unicode_compatible
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(User)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)

View File

@ -9,7 +9,6 @@ from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse, NoReverseMatch
from django.template.response import TemplateResponse
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
@ -18,12 +17,15 @@ from django.conf import settings
LOGIN_FORM_KEY = 'this_is_the_login_form'
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
class AdminSite(object):
"""
An AdminSite object encapsulates an instance of the Django admin application, ready
@ -41,7 +43,7 @@ class AdminSite(object):
password_change_done_template = None
def __init__(self, name='admin', app_name='admin'):
self._registry = {} # model_class class -> admin_class instance
self._registry = {} # model_class class -> admin_class instance
self.name = name
self.app_name = app_name
self._actions = {'delete_selected': actions.delete_selected}
@ -80,20 +82,23 @@ class AdminSite(object):
if model in self._registry:
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
# For reasons I don't quite understand, without a __module__
# the created class appears to "live" in the wrong place,
# which causes issues later on.
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Ignore the registration if the model has been
# swapped out.
if not model._meta.swapped:
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
# For reasons I don't quite understand, without a __module__
# the created class appears to "live" in the wrong place,
# which causes issues later on.
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Validate (which might be a no-op)
validate(admin_class, model)
# Validate (which might be a no-op)
validate(admin_class, model)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
def unregister(self, model_or_iterable):
"""
@ -319,6 +324,7 @@ class AdminSite(object):
REDIRECT_FIELD_NAME: request.get_full_path(),
}
context.update(extra_context or {})
defaults = {
'extra_context': context,
'current_app': self.name,

View File

@ -26,7 +26,7 @@
{% if user.is_active and user.is_staff %}
<div id="user-tools">
{% trans 'Welcome,' %}
<strong>{% filter force_escape %}{% firstof user.first_name user.username %}{% endfilter %}</strong>.
<strong>{% filter force_escape %}{% firstof user.get_short_name user.username %}{% endfilter %}</strong>.
{% block userlinks %}
{% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %}

View File

@ -30,7 +30,7 @@
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
<div class="form-row">
{% if not form.this_is_the_login_form.errors %}{{ form.username.errors }}{% endif %}
<label for="id_username" class="required">{% trans 'Username:' %}</label> {{ form.username }}
<label for="id_username" class="required">{{ form.username.label }}:</label> {{ form.username }}
</div>
<div class="form-row">
{% if not form.this_is_the_login_form.errors %}{{ form.password.errors }}{% endif %}

View File

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

View File

@ -6,9 +6,10 @@ SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
REDIRECT_FIELD_NAME = 'next'
def load_backend(path):
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
module, attr = path[:i], path[i + 1:]
try:
mod = import_module(module)
except ImportError as e:
@ -21,6 +22,7 @@ def load_backend(path):
raise ImproperlyConfigured('Module "%s" does not define a "%s" authentication backend' % (module, attr))
return cls()
def get_backends():
from django.conf import settings
backends = []
@ -30,6 +32,7 @@ def get_backends():
raise ImproperlyConfigured('No authentication backends have been defined. Does AUTHENTICATION_BACKENDS contain anything?')
return backends
def authenticate(**credentials):
"""
If the given credentials are valid, return a User object.
@ -46,6 +49,7 @@ def authenticate(**credentials):
user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
return user
def login(request, user):
"""
Persist a user id and a backend in the request. This way a user doesn't
@ -69,6 +73,7 @@ def login(request, user):
request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user)
def logout(request):
"""
Removes the authenticated user's ID from the request and flushes their
@ -86,6 +91,22 @@ def logout(request):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
def get_user_model():
"Return the User model that is active in this project"
from django.conf import settings
from django.db.models import get_model
try:
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
except ValueError:
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
user_model = get_model(app_label, model_name)
if user_model is None:
raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL)
return user_model
def get_user(request):
from django.contrib.auth.models import AnonymousUser
try:

View File

@ -1,6 +1,6 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User, Permission
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
class ModelBackend(object):
@ -12,10 +12,11 @@ class ModelBackend(object):
# configurable.
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(username=username)
UserModel = get_user_model()
user = UserModel.objects.get_by_natural_key(username)
if user.check_password(password):
return user
except User.DoesNotExist:
except UserModel.DoesNotExist:
return None
def get_group_permissions(self, user_obj, obj=None):
@ -60,8 +61,9 @@ class ModelBackend(object):
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
UserModel = get_user_model()
return UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
@ -94,17 +96,21 @@ class RemoteUserBackend(ModelBackend):
user = None
username = self.clean_username(remote_user)
UserModel = get_user_model()
# Note that this could be accomplished in one try-except clause, but
# instead we use get_or_create when creating unknown users since it has
# built-in safeguards for multiple threads.
if self.create_unknown_user:
user, created = User.objects.get_or_create(username=username)
user, created = UserModel.objects.get_or_create(**{
getattr(UserModel, 'USERNAME_FIELD', 'username'): username
})
if created:
user = self.configure_user(user)
else:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = UserModel.objects.get_by_natural_key(username)
except UserModel.DoesNotExist:
pass
return user

View File

@ -0,0 +1,14 @@
[
{
"pk": "1",
"model": "auth.customuser",
"fields": {
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
"last_login": "2006-12-17 07:03:31",
"email": "staffmember@example.com",
"is_active": true,
"is_admin": false,
"date_of_birth": "1976-11-08"
}
}
]

View File

@ -7,9 +7,10 @@ from django.utils.datastructures import SortedDict
from django.utils.html import format_html, format_html_join
from django.utils.http import int_to_base36
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib.auth import authenticate
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.models import User
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher
from django.contrib.auth.tokens import default_token_generator
@ -135,7 +136,7 @@ class AuthenticationForm(forms.Form):
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
username = forms.CharField(label=_("Username"), max_length=30)
username = forms.CharField(max_length=30)
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
error_messages = {
@ -157,6 +158,11 @@ class AuthenticationForm(forms.Form):
self.user_cache = None
super(AuthenticationForm, self).__init__(*args, **kwargs)
# Set the label for the "username" field.
UserModel = get_user_model()
username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
self.fields['username'].label = capfirst(username_field.verbose_name)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
@ -198,9 +204,10 @@ class PasswordResetForm(forms.Form):
"""
Validates that an active user exists with the given email address.
"""
UserModel = get_user_model()
email = self.cleaned_data["email"]
self.users_cache = User.objects.filter(email__iexact=email,
is_active=True)
self.users_cache = UserModel.objects.filter(email__iexact=email,
is_active=True)
if not len(self.users_cache):
raise forms.ValidationError(self.error_messages['unknown'])
if any((user.password == UNUSABLE_PASSWORD)

View File

@ -6,9 +6,10 @@ from __future__ import unicode_literals
import getpass
import locale
import unicodedata
from django.contrib.auth import models as auth_app
from django.contrib.auth import models as auth_app, get_user_model
from django.core import exceptions
from django.db.models import get_models, signals
from django.contrib.auth.models import User
from django.utils import six
from django.utils.six.moves import input
@ -64,7 +65,9 @@ def create_permissions(app, created_models, verbosity, **kwargs):
def create_superuser(app, created_models, verbosity, db, **kwargs):
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 "
"don't have any superusers defined.\nWould you like to create one "
"now? (yes/no): ")
@ -113,28 +116,35 @@ def get_default_username(check_db=True):
:returns: The username, or an empty string if no username can be
determined.
"""
from django.contrib.auth.management.commands.createsuperuser import (
RE_VALID_USERNAME)
# If the User model has been swapped out, we can't make any assumptions
# about the default user name.
if auth_app.User._meta.swapped:
return ''
default_username = get_system_username()
try:
default_username = unicodedata.normalize('NFKD', default_username)\
.encode('ascii', 'ignore').decode('ascii').replace(' ', '').lower()
except UnicodeDecodeError:
return ''
if not RE_VALID_USERNAME.match(default_username):
# Run the username validator
try:
auth_app.User._meta.get_field('username').run_validators(default_username)
except exceptions.ValidationError:
return ''
# Don't return the default username if it is already taken.
if check_db and default_username:
try:
User.objects.get(username=default_username)
except User.DoesNotExist:
auth_app.User.objects.get(username=default_username)
except auth_app.User.DoesNotExist:
pass
else:
return ''
return default_username
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,
sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser")
sender=auth_app, dispatch_uid="django.contrib.auth.management.create_superuser")

View File

@ -1,8 +1,8 @@
import getpass
from optparse import make_option
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import User
from django.db import DEFAULT_DB_ALIAS
@ -30,12 +30,16 @@ class Command(BaseCommand):
else:
username = getpass.getuser()
UserModel = get_user_model()
try:
u = User.objects.using(options.get('database')).get(username=username)
except User.DoesNotExist:
u = UserModel.objects.using(options.get('database')).get(**{
getattr(UserModel, 'USERNAME_FIELD', 'username'): username
})
except UserModel.DoesNotExist:
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
count = 0
@ -48,9 +52,9 @@ class Command(BaseCommand):
count = count + 1
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.save()
return "Password changed successfully for user '%s'" % u.username
return "Password changed successfully for user '%s'" % u

View File

@ -3,109 +3,114 @@ Management utility to create superusers.
"""
import getpass
import re
import sys
from optparse import make_option
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.contrib.auth.management import get_default_username
from django.core import exceptions
from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS
from django.utils.six.moves import input
from django.utils.translation import ugettext as _
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.'))
from django.utils.text import capfirst
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--username', dest='username', default=None,
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,
help=('Tells Django to NOT prompt the user for input of any kind. '
'You must use --username and --email with --noinput, and '
'superusers created with --noinput will not be able to log '
'in until they\'re given a valid password.')),
'You must use --username with --noinput, along with an option for '
'any other required field. Superusers created with --noinput will '
' not be able to log in until they\'re given a valid password.')),
make_option('--database', action='store', dest='database',
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 get_user_model().REQUIRED_FIELDS
)
help = 'Used to create a superuser.'
def handle(self, *args, **options):
username = options.get('username', None)
email = options.get('email', None)
interactive = options.get('interactive')
verbosity = int(options.get('verbosity', 1))
database = options.get('database')
# Do quick and dirty validation if --noinput
if not interactive:
if not username or not email:
raise CommandError("You must use --username and --email with --noinput.")
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.")
UserModel = get_user_model()
username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
other_fields = UserModel.REQUIRED_FIELDS
# If not provided, create the user with an unusable password
password = None
other_data = {}
# Prompt for username/email/password. Enclose this whole thing in a
# try/except to trap for a keyboard interrupt and exit gracefully.
if interactive:
# Do quick and dirty validation if --noinput
if not 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 as 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()
try:
# Get a username
while 1:
while username is None:
username_field = UserModel._meta.get_field(getattr(UserModel, 'USERNAME_FIELD', 'username'))
if not username:
input_msg = 'Username'
input_msg = capfirst(username_field.verbose_name)
if default_username:
input_msg += ' (leave blank to use %r)' % default_username
username = input(input_msg + ': ')
if default_username and username == '':
raw_value = input(input_msg + ': ')
if default_username and raw_value == '':
username = default_username
if not RE_VALID_USERNAME.match(username):
self.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.")
try:
username = username_field.clean(raw_value, None)
except exceptions.ValidationError as e:
self.stderr.write("Error: %s" % '; '.join(e.messages))
username = None
continue
try:
User.objects.using(database).get(username=username)
except User.DoesNotExist:
break
UserModel.objects.using(database).get(**{
getattr(UserModel, 'USERNAME_FIELD', 'username'): username
})
except UserModel.DoesNotExist:
pass
else:
self.stderr.write("Error: That username is already taken.")
username = None
# Get an email
while 1:
if not email:
email = input('E-mail address: ')
try:
is_valid_email(email)
except exceptions.ValidationError:
self.stderr.write("Error: That e-mail address is invalid.")
email = None
else:
break
for field_name in other_fields:
field = UserModel._meta.get_field(field_name)
other_data[field_name] = options.get(field_name)
while other_data[field_name] is None:
raw_value = input(capfirst(field.verbose_name + ': '))
try:
other_data[field_name] = field.clean(raw_value, None)
except exceptions.ValidationError as e:
self.stderr.write("Error: %s" % '; '.join(e.messages))
other_data[field_name] = None
# Get a password
while 1:
while password is None:
if not password:
password = getpass.getpass()
password2 = getpass.getpass('Password (again): ')
@ -117,12 +122,11 @@ class Command(BaseCommand):
self.stderr.write("Error: Blank passwords aren't allowed.")
password = None
continue
break
except KeyboardInterrupt:
self.stderr.write("\nOperation cancelled.")
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:
self.stdout.write("Superuser created successfully.")
self.stdout.write("Superuser created successfully.")

View File

@ -1,7 +1,10 @@
from __future__ import unicode_literals
import re
import warnings
from django.core.exceptions import ImproperlyConfigured
from django.core.mail import send_mail
from django.core import validators
from django.db import models
from django.db.models.manager import EmptyManager
from django.utils.crypto import get_random_string
@ -96,6 +99,7 @@ class GroupManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
@python_2_unicode_compatible
class Group(models.Model):
"""
@ -131,7 +135,7 @@ class Group(models.Model):
return (self.name,)
class UserManager(models.Manager):
class BaseUserManager(models.Manager):
@classmethod
def normalize_email(cls, email):
@ -148,30 +152,6 @@ class UserManager(models.Manager):
email = '@'.join([email_name, domain_part.lower()])
return email
def create_user(self, username, email=None, password=None):
"""
Creates and saves a User with the given username, email and password.
"""
now = timezone.now()
if not username:
raise ValueError('The given username must be set')
email = UserManager.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False,
last_login=now, date_joined=now)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password):
u = self.create_user(username, email, password)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save(using=self._db)
return u
def make_random_password(self, length=10,
allowed_chars='abcdefghjkmnpqrstuvwxyz'
'ABCDEFGHJKLMNPQRSTUVWXYZ'
@ -185,7 +165,34 @@ class UserManager(models.Manager):
return get_random_string(length, allowed_chars)
def get_by_natural_key(self, username):
return self.get(username=username)
return self.get(**{getattr(self.model, 'USERNAME_FIELD', 'username'): username})
class UserManager(BaseUserManager):
def create_user(self, username, email=None, password=None, **extra_fields):
"""
Creates and saves a User with the given username, email and password.
"""
now = timezone.now()
if not username:
raise ValueError('The given username must be set')
email = UserManager.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False,
last_login=now, date_joined=now, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password, **extra_fields):
u = self.create_user(username, email, password, **extra_fields)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save(using=self._db)
return u
# A few helper functions for common logic between User and AnonymousUser.
@ -201,8 +208,6 @@ def _user_get_all_permissions(user, obj):
def _user_has_perm(user, perm, obj):
anon = user.is_anonymous()
active = user.is_active
for backend in auth.get_backends():
if hasattr(backend, "has_perm"):
if obj is not None:
@ -215,8 +220,6 @@ def _user_has_perm(user, perm, obj):
def _user_has_module_perms(user, app_label):
anon = user.is_anonymous()
active = user.is_active
for backend in auth.get_backends():
if hasattr(backend, "has_module_perms"):
if backend.has_module_perms(user, app_label):
@ -224,53 +227,14 @@ def _user_has_module_perms(user, app_label):
return False
@python_2_unicode_compatible
class User(models.Model):
"""
Users within the Django authentication system are represented by this
model.
Username and password are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters'))
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('e-mail address'), blank=True)
class AbstractBaseUser(models.Model):
password = models.CharField(_('password'), max_length=128)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.'))
last_login = models.DateTimeField(_('last login'), default=timezone.now)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'))
user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.')
objects = UserManager()
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def __str__(self):
return self.username
def natural_key(self):
return (self.username,)
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
abstract = True
def is_anonymous(self):
"""
@ -286,13 +250,6 @@ class User(models.Model):
"""
return True
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def set_password(self, raw_password):
self.password = make_password(raw_password)
@ -313,6 +270,77 @@ class User(models.Model):
def has_usable_password(self):
return is_password_usable(self.password)
def get_full_name(self):
raise NotImplementedError()
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
admin-compliant permissions.
Username, password and email are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
])
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'))
user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.')
objects = UserManager()
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
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)
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
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):
"""
Returns a list of permission strings that this user has through his/her
@ -381,6 +409,8 @@ class User(models.Model):
Returns site-specific profile for this user. Raises
SiteProfileNotAvailable if this site does not allow profiles.
"""
warnings.warn("The use of AUTH_PROFILE_MODULE to define user profiles has been deprecated.",
PendingDeprecationWarning)
if not hasattr(self, '_profile_cache'):
from django.conf import settings
if not getattr(settings, 'AUTH_PROFILE_MODULE', False):
@ -407,6 +437,17 @@ class User(models.Model):
return self._profile_cache
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username, password and email are required. Other fields are optional.
"""
class Meta:
swappable = 'AUTH_USER_MODEL'
@python_2_unicode_compatible
class AnonymousUser(object):
id = None
@ -431,7 +472,7 @@ class AnonymousUser(object):
return not self.__eq__(other)
def __hash__(self):
return 1 # instances always return the same hash value
return 1 # instances always return the same hash value
def save(self):
raise NotImplementedError

View File

@ -1,26 +1,15 @@
from django.contrib.auth.tests.auth_backends import (BackendTest,
RowlevelBackendTest, AnonymousUserBackendTest, NoBackendsTest,
InActiveUserBackendTest)
from django.contrib.auth.tests.basic import BasicTestCase
from django.contrib.auth.tests.context_processors import AuthContextProcessorTests
from django.contrib.auth.tests.decorators import LoginRequiredTestCase
from django.contrib.auth.tests.forms import (UserCreationFormTest,
AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest,
UserChangeFormTest, PasswordResetFormTest)
from django.contrib.auth.tests.remote_user import (RemoteUserTest,
RemoteUserNoCreateTest, RemoteUserCustomTest)
from django.contrib.auth.tests.management import (
GetDefaultUsernameTestCase,
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)
from django.contrib.auth.tests.custom_user import *
from django.contrib.auth.tests.auth_backends import *
from django.contrib.auth.tests.basic import *
from django.contrib.auth.tests.context_processors import *
from django.contrib.auth.tests.decorators import *
from django.contrib.auth.tests.forms import *
from django.contrib.auth.tests.remote_user import *
from django.contrib.auth.tests.management import *
from django.contrib.auth.tests.models import *
from django.contrib.auth.tests.hashers import *
from django.contrib.auth.tests.signals import *
from django.contrib.auth.tests.tokens import *
from django.contrib.auth.tests.views import *
# The password for the fixture data users is 'password'

View File

@ -2,12 +2,14 @@ from __future__ import unicode_literals
from django.conf import settings
from django.contrib.auth.models import User, Group, Permission, AnonymousUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
from django.test.utils import override_settings
@skipIfCustomUser
class BackendTest(TestCase):
backend = 'django.contrib.auth.backends.ModelBackend'
@ -151,6 +153,7 @@ class SimpleRowlevelBackend(object):
return ['none']
@skipIfCustomUser
class RowlevelBackendTest(TestCase):
"""
Tests for auth backend that supports object level permissions
@ -223,6 +226,7 @@ class AnonymousUserBackendTest(TestCase):
self.assertEqual(self.user1.get_all_permissions(TestObj()), set(['anon']))
@skipIfCustomUser
@override_settings(AUTHENTICATION_BACKENDS=[])
class NoBackendsTest(TestCase):
"""
@ -235,6 +239,7 @@ class NoBackendsTest(TestCase):
self.assertRaises(ImproperlyConfigured, self.user.has_perm, ('perm', TestObj(),))
@skipIfCustomUser
class InActiveUserBackendTest(TestCase):
"""
Tests for a inactive user

View File

@ -1,13 +1,18 @@
import locale
import traceback
from django.contrib.auth import get_user_model
from django.contrib.auth.management.commands import createsuperuser
from django.contrib.auth.models import User, AnonymousUser
from django.contrib.auth.tests.custom_user import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core.exceptions import ImproperlyConfigured
from django.core.management import call_command
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.six import StringIO
@skipIfCustomUser
class BasicTestCase(TestCase):
def test_user(self):
"Check that users can be created and can set their password"
@ -33,7 +38,7 @@ class BasicTestCase(TestCase):
self.assertFalse(u.is_superuser)
# 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())
def test_user_no_email(self):
@ -98,7 +103,6 @@ class BasicTestCase(TestCase):
self.assertEqual(u.email, 'joe2@somewhere.org')
self.assertFalse(u.has_usable_password())
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
@ -124,15 +128,21 @@ class BasicTestCase(TestCase):
# Temporarily replace getpass to allow interactive code to be used
# non-interactively
class mock_getpass: pass
class mock_getpass:
pass
mock_getpass.getpass = staticmethod(lambda p=None: "nopasswd")
createsuperuser.getpass = mock_getpass
# Call the command in this new environment
new_io = StringIO()
call_command("createsuperuser", interactive=True, username="nolocale@somewhere.org", email="nolocale@somewhere.org", stdout=new_io)
call_command("createsuperuser",
interactive=True,
username="nolocale@somewhere.org",
email="nolocale@somewhere.org",
stdout=new_io
)
except TypeError as e:
except TypeError:
self.fail("createsuperuser fails if the OS provides no information about the current locale")
finally:
@ -143,3 +153,24 @@ class BasicTestCase(TestCase):
# If we were successful, a user should have been created
u = User.objects.get(username="nolocale@somewhere.org")
self.assertEqual(u.email, 'nolocale@somewhere.org')
def test_get_user_model(self):
"The current user model can be retrieved"
self.assertEqual(get_user_model(), User)
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
def test_swappable_user(self):
"The current user model can be swapped out for another"
self.assertEqual(get_user_model(), CustomUser)
@override_settings(AUTH_USER_MODEL='badsetting')
def test_swappable_user_bad_setting(self):
"The alternate user setting must point to something in the format app.model"
with self.assertRaises(ImproperlyConfigured):
get_user_model()
@override_settings(AUTH_USER_MODEL='thismodel.doesntexist')
def test_swappable_user_nonexistent_model(self):
"The current user model must point to an installed model"
with self.assertRaises(ImproperlyConfigured):
get_user_model()

View File

@ -2,12 +2,13 @@ import os
from django.conf import global_settings
from django.contrib.auth import authenticate
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.db.models import Q
from django.template import context
from django.test import TestCase
from django.test.utils import override_settings
@skipIfCustomUser
@override_settings(
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'),

View File

@ -0,0 +1,75 @@
# 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_active = models.BooleanField(default=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

View File

@ -1,7 +1,9 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.tests.views import AuthViewsTestCase
from django.contrib.auth.tests.utils import skipIfCustomUser
@skipIfCustomUser
class LoginRequiredTestCase(AuthViewsTestCase):
"""
Tests the login_required decorators

View File

@ -4,16 +4,17 @@ import os
from django.contrib.auth.models import User
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm)
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core import mail
from django.forms.fields import Field, EmailField
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.encoding import force_text
from django.utils import six
from django.utils import translation
from django.utils.translation import ugettext as _
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class UserCreationFormTest(TestCase):
@ -81,6 +82,7 @@ class UserCreationFormTest(TestCase):
self.assertEqual(repr(u), '<User: jsmith@example.com>')
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AuthenticationFormTest(TestCase):
@ -133,6 +135,7 @@ class AuthenticationFormTest(TestCase):
self.assertEqual(form.non_field_errors(), [])
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class SetPasswordFormTest(TestCase):
@ -160,6 +163,7 @@ class SetPasswordFormTest(TestCase):
self.assertTrue(form.is_valid())
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class PasswordChangeFormTest(TestCase):
@ -208,6 +212,7 @@ class PasswordChangeFormTest(TestCase):
['old_password', 'new_password1', 'new_password2'])
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class UserChangeFormTest(TestCase):
@ -261,6 +266,7 @@ class UserChangeFormTest(TestCase):
form.as_table())
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class PasswordResetFormTest(TestCase):

View File

@ -1,13 +1,20 @@
from __future__ import unicode_literals
from datetime import date
from django.contrib.auth import models, management
from django.contrib.auth.management.commands import changepassword
from django.contrib.auth.models import User
from django.contrib.auth.tests import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
from django.utils.six import StringIO
@skipIfCustomUser
class GetDefaultUsernameTestCase(TestCase):
def setUp(self):
@ -36,6 +43,7 @@ class GetDefaultUsernameTestCase(TestCase):
self.assertEqual(management.get_default_username(), 'julia')
@skipIfCustomUser
class ChangepasswordManagementCommandTestCase(TestCase):
def setUp(self):
@ -48,7 +56,7 @@ class ChangepasswordManagementCommandTestCase(TestCase):
self.stderr.close()
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'))
command = changepassword.Command()
command._get_pass = lambda *args: 'not qwerty'
@ -69,3 +77,93 @@ class ChangepasswordManagementCommandTestCase(TestCase):
with self.assertRaises(CommandError):
command.execute("joe", stdout=self.stdout, stderr=self.stderr)
@skipIfCustomUser
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

@ -1,11 +1,13 @@
from django.conf import settings
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
UserManager)
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
@skipIfCustomUser
@override_settings(USE_TZ=False, AUTH_PROFILE_MODULE='')
class ProfileTestCase(TestCase):
@ -31,6 +33,7 @@ class ProfileTestCase(TestCase):
user.get_profile()
@skipIfCustomUser
@override_settings(USE_TZ=False)
class NaturalKeysTestCase(TestCase):
fixtures = ['authtestdata.json']
@ -45,6 +48,7 @@ class NaturalKeysTestCase(TestCase):
self.assertEqual(Group.objects.get_by_natural_key('users'), users_group)
@skipIfCustomUser
@override_settings(USE_TZ=False)
class LoadDataWithoutNaturalKeysTestCase(TestCase):
fixtures = ['regular.json']
@ -55,6 +59,7 @@ class LoadDataWithoutNaturalKeysTestCase(TestCase):
self.assertEqual(group, user.groups.get())
@skipIfCustomUser
@override_settings(USE_TZ=False)
class LoadDataWithNaturalKeysTestCase(TestCase):
fixtures = ['natural.json']
@ -65,6 +70,7 @@ class LoadDataWithNaturalKeysTestCase(TestCase):
self.assertEqual(group, user.groups.get())
@skipIfCustomUser
class UserManagerTestCase(TestCase):
def test_create_user(self):

View File

@ -3,10 +3,12 @@ from datetime import datetime
from django.conf import settings
from django.contrib.auth.backends import RemoteUserBackend
from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.utils import timezone
@skipIfCustomUser
class RemoteUserTest(TestCase):
urls = 'django.contrib.auth.tests.urls'
@ -106,6 +108,7 @@ class RemoteUserNoCreateBackend(RemoteUserBackend):
create_unknown_user = False
@skipIfCustomUser
class RemoteUserNoCreateTest(RemoteUserTest):
"""
Contains the same tests as RemoteUserTest, but using a custom auth backend
@ -142,6 +145,7 @@ class CustomRemoteUserBackend(RemoteUserBackend):
return user
@skipIfCustomUser
class RemoteUserCustomTest(RemoteUserTest):
"""
Tests a custom RemoteUserBackend subclass that overrides the clean_username

View File

@ -1,10 +1,12 @@
from django.contrib.auth import signals
from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class SignalTestCase(TestCase):
urls = 'django.contrib.auth.tests.urls'

View File

@ -4,10 +4,12 @@ from datetime import date, timedelta
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.utils import unittest
@skipIfCustomUser
class TokenGeneratorTest(TestCase):
def test_make_token(self):

View File

@ -0,0 +1,9 @@
from django.conf import settings
from django.utils.unittest import skipIf
def skipIfCustomUser(test_func):
"""
Skip a test if a custom user model is in use.
"""
return skipIf(settings.AUTH_USER_MODEL != 'auth.User', 'Custom user model in use')(test_func)

View File

@ -16,6 +16,7 @@ from django.test.utils import override_settings
from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
SetPasswordForm, PasswordResetForm)
from django.contrib.auth.tests.utils import skipIfCustomUser
@override_settings(
@ -50,6 +51,7 @@ class AuthViewsTestCase(TestCase):
return self.assertContains(response, escape(force_text(text)), **kwargs)
@skipIfCustomUser
class AuthViewNamedURLTests(AuthViewsTestCase):
urls = 'django.contrib.auth.urls'
@ -75,6 +77,7 @@ class AuthViewNamedURLTests(AuthViewsTestCase):
self.fail("Reversal of url named '%s' failed with NoReverseMatch" % name)
@skipIfCustomUser
class PasswordResetTest(AuthViewsTestCase):
def test_email_not_found(self):
@ -172,6 +175,30 @@ class PasswordResetTest(AuthViewsTestCase):
self.assertContainsEscaped(response, SetPasswordForm.error_messages['password_mismatch'])
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
class CustomUserPasswordResetTest(AuthViewsTestCase):
fixtures = ['custom_user.json']
def _test_confirm_start(self):
# Start by creating the email
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
return self._read_signup_email(mail.outbox[0])
def _read_signup_email(self, email):
urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body)
self.assertTrue(urlmatch is not None, "No URL found in sent email")
return urlmatch.group(), urlmatch.groups()[0]
def test_confirm_valid_custom_user(self):
url, path = self._test_confirm_start()
response = self.client.get(path)
# redirect to a 'complete' page:
self.assertContains(response, "Please enter your new password")
@skipIfCustomUser
class ChangePasswordTest(AuthViewsTestCase):
def fail_login(self, password='password'):
@ -231,6 +258,7 @@ class ChangePasswordTest(AuthViewsTestCase):
self.assertTrue(response['Location'].endswith('/login/?next=/password_change/done/'))
@skipIfCustomUser
class LoginTest(AuthViewsTestCase):
def test_current_site_in_context_after_login(self):
@ -289,6 +317,7 @@ class LoginTest(AuthViewsTestCase):
"%s should be allowed" % good_url)
@skipIfCustomUser
class LoginURLSettings(AuthViewsTestCase):
def setUp(self):
@ -347,6 +376,7 @@ class LoginURLSettings(AuthViewsTestCase):
querystring.urlencode('/')))
@skipIfCustomUser
class LogoutTest(AuthViewsTestCase):
def confirm_logged_out(self):

View File

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

View File

@ -15,10 +15,9 @@ from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
# Avoid shadowing the login() and logout() views below.
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout, get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, PasswordChangeForm
from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.models import get_current_site
@ -74,6 +73,7 @@ def login(request, template_name='registration/login.html',
return TemplateResponse(request, template_name, context,
current_app=current_app)
def logout(request, next_page=None,
template_name='registration/logged_out.html',
redirect_field_name=REDIRECT_FIELD_NAME,
@ -104,6 +104,7 @@ def logout(request, next_page=None,
# Redirect to this page until the session has been cleared.
return HttpResponseRedirect(next_page or request.path)
def logout_then_login(request, login_url=None, current_app=None, extra_context=None):
"""
Logs out the user if he is logged in. Then redirects to the log-in page.
@ -113,6 +114,7 @@ def logout_then_login(request, login_url=None, current_app=None, extra_context=N
login_url = resolve_url(login_url)
return logout(request, login_url, current_app=current_app, extra_context=extra_context)
def redirect_to_login(next, login_url=None,
redirect_field_name=REDIRECT_FIELD_NAME):
"""
@ -128,6 +130,7 @@ def redirect_to_login(next, login_url=None,
return HttpResponseRedirect(urlunparse(login_url_parts))
# 4 views for password reset:
# - password_reset sends the mail
# - password_reset_done shows a success message for the above
@ -173,6 +176,7 @@ def password_reset(request, is_admin_site=False,
return TemplateResponse(request, template_name, context,
current_app=current_app)
def password_reset_done(request,
template_name='registration/password_reset_done.html',
current_app=None, extra_context=None):
@ -182,6 +186,7 @@ def password_reset_done(request,
return TemplateResponse(request, template_name, context,
current_app=current_app)
# Doesn't need csrf_protect since no-one can guess the URL
@sensitive_post_parameters()
@never_cache
@ -195,13 +200,14 @@ def password_reset_confirm(request, uidb36=None, token=None,
View that checks the hash in a password reset link and presents a
form for entering a new password.
"""
assert uidb36 is not None and token is not None # checked by URLconf
UserModel = get_user_model()
assert uidb36 is not None and token is not None # checked by URLconf
if post_reset_redirect is None:
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
try:
uid_int = base36_to_int(uidb36)
user = User.objects.get(id=uid_int)
except (ValueError, OverflowError, User.DoesNotExist):
user = UserModel.objects.get(id=uid_int)
except (ValueError, OverflowError, UserModel.DoesNotExist):
user = None
if user is not None and token_generator.check_token(user, token):
@ -225,6 +231,7 @@ def password_reset_confirm(request, uidb36=None, token=None,
return TemplateResponse(request, template_name, context,
current_app=current_app)
def password_reset_complete(request,
template_name='registration/password_reset_complete.html',
current_app=None, extra_context=None):
@ -236,6 +243,7 @@ def password_reset_complete(request,
return TemplateResponse(request, template_name, context,
current_app=current_app)
@sensitive_post_parameters()
@csrf_protect
@login_required
@ -261,6 +269,7 @@ def password_change(request,
return TemplateResponse(request, template_name, context,
current_app=current_app)
@login_required
def password_change_done(request,
template_name='registration/password_change_done.html',

View File

@ -1,16 +1,16 @@
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib.comments.managers import CommentManager
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.db import models
from django.core import urlresolvers
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000)
COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
class BaseCommentAbstractModel(models.Model):
"""
@ -40,6 +40,7 @@ class BaseCommentAbstractModel(models.Model):
args=(self.content_type_id, self.object_pk)
)
@python_2_unicode_compatible
class Comment(BaseCommentAbstractModel):
"""
@ -49,7 +50,7 @@ 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(User, 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)
@ -117,6 +118,7 @@ class Comment(BaseCommentAbstractModel):
def _get_name(self):
return self.userinfo["name"]
def _set_name(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "\
@ -126,6 +128,7 @@ class Comment(BaseCommentAbstractModel):
def _get_email(self):
return self.userinfo["email"]
def _set_email(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "\
@ -135,6 +138,7 @@ class Comment(BaseCommentAbstractModel):
def _get_url(self):
return self.userinfo["url"]
def _set_url(self, val):
self.user_url = val
url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")
@ -155,6 +159,7 @@ class Comment(BaseCommentAbstractModel):
}
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
@python_2_unicode_compatible
class CommentFlag(models.Model):
"""
@ -169,7 +174,7 @@ 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(User, verbose_name=_('user'), related_name="comment_flags")
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)

View File

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

View File

@ -6,6 +6,7 @@ from django.core.management.base import AppCommand
from django.core.management.sql import sql_all
from django.db import connections, DEFAULT_DB_ALIAS
class Command(AppCommand):
help = "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s)."

View File

@ -68,6 +68,7 @@ class Command(NoArgsCommand):
if router.allow_syncdb(db, m)])
for app in models.get_apps()
]
def model_installed(model):
opts = model._meta
converter = connection.introspection.table_name_converter
@ -101,7 +102,6 @@ class Command(NoArgsCommand):
cursor.execute(statement)
tables.append(connection.introspection.table_name_converter(model._meta.db_table))
transaction.commit_unless_managed(using=db)
# Send the post_syncdb signal, so individual apps can do whatever they need

View File

@ -1,5 +1,6 @@
from django.core.management.base import NoArgsCommand
class Command(NoArgsCommand):
help = "Validates all installed models."

View File

@ -9,6 +9,7 @@ from django.core.management.base import CommandError
from django.db import models
from django.db.models import get_models
def sql_create(app, style, connection):
"Returns a list of the CREATE TABLE SQL statements for the given app."
@ -55,6 +56,7 @@ def sql_create(app, style, connection):
return final_output
def sql_delete(app, style, connection):
"Returns a list of the DROP TABLE SQL statements for the given app."
@ -83,7 +85,7 @@ def sql_delete(app, style, connection):
opts = model._meta
for f in opts.local_fields:
if f.rel and f.rel.to not in to_delete:
references_to_delete.setdefault(f.rel.to, []).append( (model, f) )
references_to_delete.setdefault(f.rel.to, []).append((model, f))
to_delete.add(model)
@ -97,7 +99,8 @@ def sql_delete(app, style, connection):
cursor.close()
connection.close()
return output[::-1] # Reverse it, to deal with table dependencies.
return output[::-1] # Reverse it, to deal with table dependencies.
def sql_flush(style, connection, only_django=False, reset_sequences=True):
"""
@ -114,6 +117,7 @@ def sql_flush(style, connection, only_django=False, reset_sequences=True):
statements = connection.ops.sql_flush(style, tables, seqs)
return statements
def sql_custom(app, style, connection):
"Returns a list of the custom table modifying SQL statements for the given app."
output = []
@ -125,6 +129,7 @@ def sql_custom(app, style, connection):
return output
def sql_indexes(app, style, connection):
"Returns a list of the CREATE INDEX SQL statements for all models in the given app."
output = []
@ -132,10 +137,12 @@ def sql_indexes(app, style, connection):
output.extend(connection.creation.sql_indexes_for_model(model, style))
return output
def sql_all(app, style, connection):
"Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
return sql_create(app, style, connection) + sql_custom(app, style, connection) + sql_indexes(app, style, connection)
def _split_statements(content):
comment_re = re.compile(r"^((?:'[^']*'|[^'])*?)--.*$")
statements = []
@ -150,6 +157,7 @@ def _split_statements(content):
statement = ""
return statements
def custom_sql_for_model(model, style, connection):
opts = model._meta
app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql'))

View File

@ -5,6 +5,7 @@ from django.utils.encoding import force_str
from django.utils.itercompat import is_iterable
from django.utils import six
class ModelErrorCollection:
def __init__(self, outfile=sys.stdout):
self.errors = []
@ -15,6 +16,7 @@ class ModelErrorCollection:
self.errors.append((context, error))
self.outfile.write(self.style.ERROR(force_str("%s: %s\n" % (context, error))))
def get_validation_errors(outfile, app=None):
"""
Validates all models that are part of the specified app. If no app name is provided,
@ -56,7 +58,7 @@ def get_validation_errors(outfile, app=None):
e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name)
if isinstance(f, models.DecimalField):
decimalp_ok, mdigits_ok = False, False
decimalp_msg ='"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
decimalp_msg = '"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
try:
decimal_places = int(f.decimal_places)
if decimal_places < 0:
@ -123,6 +125,10 @@ def get_validation_errors(outfile, app=None):
if isinstance(f.rel.to, six.string_types):
continue
# Make sure the model we're related hasn't been swapped out
if f.rel.to._meta.swapped:
e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
# Make sure the related field specified by a ForeignKey is unique
if not f.rel.to._meta.get_field(f.rel.field_name).unique:
e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.rel.field_name, f.rel.to.__name__))
@ -165,6 +171,10 @@ def get_validation_errors(outfile, app=None):
if isinstance(f.rel.to, six.string_types):
continue
# Make sure the model we're related hasn't been swapped out
if f.rel.to._meta.swapped:
e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
# Check that the field is not set to unique. ManyToManyFields do not support unique.
if f.unique:
e.add(opts, "ManyToManyFields cannot be unique. Remove the unique argument on '%s'." % f.name)
@ -176,7 +186,7 @@ def get_validation_errors(outfile, app=None):
seen_from, seen_to, seen_self = False, False, 0
for inter_field in f.rel.through._meta.fields:
rel_to = getattr(inter_field.rel, 'to', None)
if from_model == to_model: # relation to self
if from_model == to_model: # relation to self
if rel_to == from_model:
seen_self += 1
if seen_self > 2:
@ -275,10 +285,21 @@ def get_validation_errors(outfile, app=None):
if r.get_accessor_name() == rel_query_name:
e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
# Check swappable attribute.
if opts.swapped:
try:
app_label, model_name = opts.swapped.split('.')
except ValueError:
e.add(opts, "%s is not of the form 'app_label.app_name'." % opts.swappable)
continue
if not models.get_model(app_label, model_name):
e.add(opts, "Model has been swapped out for '%s' which has not been installed or is abstract." % opts.swapped)
# Check ordering attribute.
if opts.ordering:
for field_name in opts.ordering:
if field_name == '?': continue
if field_name == '?':
continue
if field_name.startswith('-'):
field_name = field_name[1:]
if opts.order_with_respect_to and field_name == '_order':

View File

@ -15,6 +15,7 @@ from django.utils import six
# These values, if given to validate(), will trigger the self.required check.
EMPTY_VALUES = (None, '', [], (), {})
class RegexValidator(object):
regex = ''
message = _('Enter a valid value.')
@ -39,14 +40,15 @@ class RegexValidator(object):
if not self.regex.search(force_text(value)):
raise ValidationError(self.message, code=self.code)
class URLValidator(RegexValidator):
regex = re.compile(
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'localhost|' #localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
r'(?::\d+)?' # optional port
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'localhost|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
def __call__(self, value):
@ -58,8 +60,8 @@ class URLValidator(RegexValidator):
value = force_text(value)
scheme, netloc, path, query, fragment = urlsplit(value)
try:
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
except UnicodeError: # invalid domain part
netloc = netloc.encode('idna').decode('ascii') # IDN -> ACE
except UnicodeError: # invalid domain part
raise e
url = urlunsplit((scheme, netloc, path, query, fragment))
super(URLValidator, self).__call__(url)
@ -75,6 +77,7 @@ def validate_integer(value):
except (ValueError, TypeError):
raise ValidationError('')
class EmailValidator(RegexValidator):
def __call__(self, value):
@ -106,10 +109,12 @@ validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of le
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, _('Enter a valid IPv4 address.'), 'invalid')
def validate_ipv6_address(value):
if not is_valid_ipv6_address(value):
raise ValidationError(_('Enter a valid IPv6 address.'), code='invalid')
def validate_ipv46_address(value):
try:
validate_ipv4_address(value)
@ -125,6 +130,7 @@ ip_address_validator_map = {
'ipv6': ([validate_ipv6_address], _('Enter a valid IPv6 address.')),
}
def ip_address_validators(protocol, unpack_ipv4):
"""
Depending on the given parameters returns the appropriate validators for
@ -147,7 +153,7 @@ validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_
class BaseValidator(object):
compare = lambda self, a, b: a is not b
clean = lambda self, x: x
clean = lambda self, x: x
message = _('Ensure this value is %(limit_value)s (it is %(show_value)s).')
code = 'limit_value'
@ -164,25 +170,28 @@ class BaseValidator(object):
params=params,
)
class MaxValueValidator(BaseValidator):
compare = lambda self, a, b: a > b
message = _('Ensure this value is less than or equal to %(limit_value)s.')
code = 'max_value'
class MinValueValidator(BaseValidator):
compare = lambda self, a, b: a < b
message = _('Ensure this value is greater than or equal to %(limit_value)s.')
code = 'min_value'
class MinLengthValidator(BaseValidator):
compare = lambda self, a, b: a < b
clean = lambda self, x: len(x)
clean = lambda self, x: len(x)
message = _('Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).')
code = 'min_length'
class MaxLengthValidator(BaseValidator):
compare = lambda self, a, b: a > b
clean = lambda self, x: len(x)
clean = lambda self, x: len(x)
message = _('Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).')
code = 'max_length'

View File

@ -319,6 +319,7 @@ class BaseDatabaseWrapper(object):
def make_debug_cursor(self, cursor):
return util.CursorDebugWrapper(cursor, self)
class BaseDatabaseFeatures(object):
allows_group_by_pk = False
# True if django.db.backend.utils.typecast_timestamp is used on values
@ -776,7 +777,7 @@ class BaseDatabaseOperations(object):
The `style` argument is a Style object as returned by either
color_style() or no_style() in django.core.management.color.
"""
return [] # No sequence reset required by default.
return [] # No sequence reset required by default.
def start_transaction_sql(self):
"""
@ -915,6 +916,7 @@ class BaseDatabaseOperations(object):
conn = ' %s ' % connector
return conn.join(sub_expressions)
class BaseDatabaseIntrospection(object):
"""
This class encapsulates all backend-specific introspection utilities
@ -1010,12 +1012,14 @@ class BaseDatabaseIntrospection(object):
for model in models.get_models(app):
if not model._meta.managed:
continue
if model._meta.swapped:
continue
if not router.allow_syncdb(self.connection.alias, model):
continue
for f in model._meta.local_fields:
if isinstance(f, models.AutoField):
sequence_list.append({'table': model._meta.db_table, 'column': f.column})
break # Only one AutoField is allowed per model, so don't bother continuing.
break # Only one AutoField is allowed per model, so don't bother continuing.
for f in model._meta.local_many_to_many:
# If this is an m2m using an intermediate table,
@ -1052,6 +1056,7 @@ class BaseDatabaseIntrospection(object):
"""
raise NotImplementedError
class BaseDatabaseClient(object):
"""
This class encapsulates all backend-specific methods for opening a
@ -1068,6 +1073,7 @@ class BaseDatabaseClient(object):
def runshell(self):
raise NotImplementedError()
class BaseDatabaseValidation(object):
"""
This class encapsualtes all backend-specific model validation.

View File

@ -40,7 +40,7 @@ class BaseDatabaseCreation(object):
(list_of_sql, pending_references_dict)
"""
opts = model._meta
if not opts.managed or opts.proxy:
if not opts.managed or opts.proxy or opts.swapped:
return [], {}
final_output = []
table_output = []
@ -92,9 +92,9 @@ class BaseDatabaseCreation(object):
full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' +
style.SQL_TABLE(qn(opts.db_table)) + ' (']
for i, line in enumerate(table_output): # Combine and add commas.
for i, line in enumerate(table_output): # Combine and add commas.
full_statement.append(
' %s%s' % (line, i < len(table_output)-1 and ',' or ''))
' %s%s' % (line, i < len(table_output) - 1 and ',' or ''))
full_statement.append(')')
if opts.db_tablespace:
tablespace_sql = self.connection.ops.tablespace_sql(
@ -143,11 +143,11 @@ class BaseDatabaseCreation(object):
"""
from django.db.backends.util import truncate_name
if not model._meta.managed or model._meta.proxy:
opts = model._meta
if not opts.managed or opts.proxy or opts.swapped:
return []
qn = self.connection.ops.quote_name
final_output = []
opts = model._meta
if model in pending_references:
for rel_class, f in pending_references[model]:
rel_opts = rel_class._meta
@ -172,7 +172,7 @@ class BaseDatabaseCreation(object):
"""
Returns the CREATE INDEX SQL statements for a single model.
"""
if not model._meta.managed or model._meta.proxy:
if not model._meta.managed or model._meta.proxy or model._meta.swapped:
return []
output = []
for f in model._meta.local_fields:
@ -211,7 +211,7 @@ class BaseDatabaseCreation(object):
Return the DROP TABLE and restraint dropping statements for a single
model.
"""
if not model._meta.managed or model._meta.proxy:
if not model._meta.managed or model._meta.proxy or model._meta.swapped:
return []
# Drop the table now
qn = self.connection.ops.quote_name
@ -228,7 +228,7 @@ class BaseDatabaseCreation(object):
def sql_remove_table_constraints(self, model, references_to_delete, style):
from django.db.backends.util import truncate_name
if not model._meta.managed or model._meta.proxy:
if not model._meta.managed or model._meta.proxy or model._meta.swapped:
return []
output = []
qn = self.connection.ops.quote_name

View File

@ -5,7 +5,7 @@ import sys
from functools import update_wrapper
from django.utils.six.moves import zip
import django.db.models.manager # Imported to register signal handler.
import django.db.models.manager # Imported to register signal handler.
from django.conf import settings
from django.core.exceptions import (ObjectDoesNotExist,
MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS)
@ -108,6 +108,11 @@ class ModelBase(type):
is_proxy = new_class._meta.proxy
# If the model is a proxy, ensure that the base class
# hasn't been swapped out.
if is_proxy and base_meta and base_meta.swapped:
raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped))
if getattr(new_class, '_default_manager', None):
if not is_proxy:
# Multi-table inheritance doesn't inherit default manager from
@ -262,6 +267,7 @@ class ModelBase(type):
if opts.order_with_respect_to:
cls.get_next_in_order = curry(cls._get_next_or_previous_in_order, is_next=True)
cls.get_previous_in_order = curry(cls._get_next_or_previous_in_order, is_next=False)
# defer creating accessors on the foreign class until we are
# certain it has been created
def make_foreign_order_accessors(field, model, cls):
@ -292,6 +298,7 @@ class ModelBase(type):
signals.class_prepared.send(sender=cls)
class ModelState(object):
"""
A class for storing instance state
@ -303,6 +310,7 @@ class ModelState(object):
# This impacts validation only; it has no effect on the actual save.
self.adding = True
class Model(six.with_metaclass(ModelBase, object)):
_deferred = False
@ -632,7 +640,6 @@ class Model(six.with_metaclass(ModelBase, object)):
signals.post_save.send(sender=origin, instance=self, created=(not record_exists),
update_fields=update_fields, raw=raw, using=using)
save_base.alters_data = True
def delete(self, using=None):
@ -656,7 +663,7 @@ class Model(six.with_metaclass(ModelBase, object)):
order = not is_next and '-' or ''
param = force_text(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
q = q|Q(**{field.name: param, 'pk__%s' % op: self.pk})
q = q | Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = self.__class__._default_manager.using(self._state.db).filter(**kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
return qs[0]
@ -849,7 +856,7 @@ class Model(six.with_metaclass(ModelBase, object)):
field = opts.get_field(field_name)
field_label = capfirst(field.verbose_name)
# Insert the error into the error dict, very sneaky
return field.error_messages['unique'] % {
return field.error_messages['unique'] % {
'model_name': six.text_type(model_name),
'field_label': six.text_type(field_label)
}
@ -857,7 +864,7 @@ class Model(six.with_metaclass(ModelBase, object)):
else:
field_labels = [capfirst(opts.get_field(f).verbose_name) for f in unique_check]
field_labels = get_text_list(field_labels, _('and'))
return _("%(model_name)s with this %(field_label)s already exists.") % {
return _("%(model_name)s with this %(field_label)s already exists.") % {
'model_name': six.text_type(model_name),
'field_label': six.text_type(field_labels)
}
@ -921,7 +928,6 @@ class Model(six.with_metaclass(ModelBase, object)):
raise ValidationError(errors)
############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
############################################
@ -963,6 +969,7 @@ def get_absolute_url(opts, func, self, *args, **kwargs):
class Empty(object):
pass
def model_unpickle(model, attrs):
"""
Used to unpickle Model subclasses with deferred fields.
@ -971,6 +978,7 @@ def model_unpickle(model, attrs):
return cls.__new__(cls)
model_unpickle.__safe_for_unpickle__ = True
def unpickle_inner_exception(klass, exception_name):
# Get the exception class from the class it is attached to:
exception = getattr(klass, exception_name)

View File

@ -21,6 +21,7 @@ RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
pending_lookups = {}
def add_lazy_relation(cls, field, relation, operation):
"""
Adds a lookup on ``cls`` when a related field is defined using a string,
@ -77,6 +78,7 @@ def add_lazy_relation(cls, field, relation, operation):
value = (cls, field, operation)
pending_lookups.setdefault(key, []).append(value)
def do_pending_lookups(sender, **kwargs):
"""
Handle any pending relations to the sending model. Sent from class_prepared.
@ -87,6 +89,7 @@ def do_pending_lookups(sender, **kwargs):
signals.class_prepared.connect(do_pending_lookups)
#HACK
class RelatedField(object):
def contribute_to_class(self, cls, name):
@ -220,6 +223,7 @@ class RelatedField(object):
# "related_name" option.
return self.rel.related_name or self.opts.object_name.lower()
class SingleRelatedObjectDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
@ -306,6 +310,7 @@ class SingleRelatedObjectDescriptor(object):
setattr(instance, self.cache_name, value)
setattr(value, self.related.field.get_cache_name(), instance)
class ReverseSingleRelatedObjectDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
@ -430,6 +435,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
if value is not None and not self.field.rel.multiple:
setattr(value, self.field.related.get_cache_name(), instance)
class ForeignRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
@ -660,7 +666,7 @@ def create_many_related_manager(superclass, rel):
for obj in objs:
if isinstance(obj, self.model):
if not router.allow_relation(obj, self.instance):
raise ValueError('Cannot add "%r": instance is on database "%s", value is on database "%s"' %
raise ValueError('Cannot add "%r": instance is on database "%s", value is on database "%s"' %
(obj, self.instance._state.db, obj._state.db))
new_ids.add(obj.pk)
elif isinstance(obj, Model):
@ -752,6 +758,7 @@ def create_many_related_manager(superclass, rel):
return ManyRelatedManager
class ManyRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
@ -860,12 +867,13 @@ class ReverseManyRelatedObjectsDescriptor(object):
manager.clear()
manager.add(*value)
class ManyToOneRel(object):
def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None):
try:
to._meta
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert isinstance(to, six.string_types), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
self.to, self.field_name = to, field_name
self.related_name = related_name
@ -891,6 +899,7 @@ class ManyToOneRel(object):
self.field_name)
return data[0]
class OneToOneRel(ManyToOneRel):
def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
parent_link=False, on_delete=None):
@ -900,6 +909,7 @@ class OneToOneRel(ManyToOneRel):
)
self.multiple = False
class ManyToManyRel(object):
def __init__(self, to, related_name=None, limit_choices_to=None,
symmetrical=True, through=None):
@ -924,16 +934,18 @@ class ManyToManyRel(object):
"""
return self.to._meta.pk
class ForeignKey(RelatedField, Field):
empty_strings_allowed = False
default_error_messages = {
'invalid': _('Model %(model)s with pk %(pk)r does not exist.')
}
description = _("Foreign Key (type determined by related field)")
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try:
to_name = to._meta.object_name.lower()
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert isinstance(to, six.string_types), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
else:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
@ -1050,6 +1062,7 @@ class ForeignKey(RelatedField, Field):
return IntegerField().db_type(connection=connection)
return rel_field.db_type(connection=connection)
class OneToOneField(ForeignKey):
"""
A OneToOneField is essentially the same as a ForeignKey, with the exception
@ -1058,6 +1071,7 @@ class OneToOneField(ForeignKey):
rather than returning a list.
"""
description = _("One-to-one relationship")
def __init__(self, to, to_field=None, **kwargs):
kwargs['unique'] = True
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@ -1077,12 +1091,14 @@ class OneToOneField(ForeignKey):
else:
setattr(instance, self.attname, data)
def create_many_to_many_intermediary_model(field, klass):
from django.db import models
managed = True
if isinstance(field.rel.to, six.string_types) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
to_model = field.rel.to
to = to_model.split('.')[-1]
def set_managed(field, model, cls):
field.rel.through._meta.managed = model._meta.managed or cls._meta.managed
add_lazy_relation(klass, field, to_model, set_managed)
@ -1119,12 +1135,14 @@ def create_many_to_many_intermediary_model(field, klass):
to: models.ForeignKey(to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace)
})
class ManyToManyField(RelatedField, Field):
description = _("Many-to-many relationship")
def __init__(self, to, **kwargs):
try:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
assert isinstance(to, six.string_types), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
# Python 2.6 and earlier require dictionary keys to be of str type,
# not unicode and class names must be ASCII (in Python 2.x), so we
@ -1135,7 +1153,7 @@ class ManyToManyField(RelatedField, Field):
kwargs['rel'] = ManyToManyRel(to,
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
symmetrical=kwargs.pop('symmetrical', to==RECURSIVE_RELATIONSHIP_CONSTANT),
symmetrical=kwargs.pop('symmetrical', to == RECURSIVE_RELATIONSHIP_CONSTANT),
through=kwargs.pop('through', None))
self.db_table = kwargs.pop('db_table', None)
@ -1166,7 +1184,7 @@ class ManyToManyField(RelatedField, Field):
if hasattr(self, cache_attr):
return getattr(self, cache_attr)
for f in self.rel.through._meta.fields:
if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
if hasattr(f, 'rel') and f.rel and f.rel.to == related.model:
setattr(self, cache_attr, getattr(f, attr))
return getattr(self, cache_attr)
@ -1177,7 +1195,7 @@ class ManyToManyField(RelatedField, Field):
return getattr(self, cache_attr)
found = False
for f in self.rel.through._meta.fields:
if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
if hasattr(f, 'rel') and f.rel and f.rel.to == related.parent_model:
if related.model == related.parent_model:
# If this is an m2m-intermediate to self,
# the first foreign key you find will be
@ -1222,7 +1240,8 @@ class ManyToManyField(RelatedField, Field):
# The intermediate m2m model is not auto created if:
# 1) There is a manually specified intermediate, or
# 2) The class owning the m2m field is abstract.
if not self.rel.through and not cls._meta.abstract:
# 3) The class owning the m2m field has been swapped out.
if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped:
self.rel.through = create_many_to_many_intermediary_model(self, cls)
# Add the descriptor for the m2m relation

View File

@ -14,6 +14,7 @@ import os
__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
'load_app', 'app_cache_ready')
class AppCache(object):
"""
A cache that stores installed applications and their models. Used to

View File

@ -13,7 +13,7 @@ def ensure_default_manager(sender, **kwargs):
_default_manager if it's not a subclass of Manager).
"""
cls = sender
if cls._meta.abstract:
if cls._meta.abstract or cls._meta.swapped:
return
if not getattr(cls, '_default_manager', None):
# Create the default manager, if needed.
@ -42,6 +42,7 @@ def ensure_default_manager(sender, **kwargs):
signals.class_prepared.connect(ensure_default_manager)
class Manager(object):
# Tracks each time a Manager instance is created. Used to retain order.
creation_counter = 0
@ -56,7 +57,9 @@ class Manager(object):
def contribute_to_class(self, model, name):
# TODO: Use weakref because of possible memory leak / circular reference.
self.model = model
setattr(model, name, ManagerDescriptor(self))
# Only contribute the manager if the model is concrete
if not model._meta.abstract and not model._meta.swapped:
setattr(model, name, ManagerDescriptor(self))
if not getattr(model, '_default_manager', None) or self.creation_counter < model._default_manager.creation_counter:
model._default_manager = self
if model._meta.abstract or (self._inherited and not self.model._meta.proxy):
@ -208,6 +211,7 @@ class Manager(object):
def raw(self, raw_query, params=None, *args, **kwargs):
return RawQuerySet(raw_query=raw_query, model=self.model, params=params, using=self._db, *args, **kwargs)
class ManagerDescriptor(object):
# This class ensures managers aren't accessible via model instances.
# For example, Poll.objects works, but poll_obj.objects raises AttributeError.
@ -219,6 +223,7 @@ class ManagerDescriptor(object):
raise AttributeError("Manager isn't accessible via %s instances" % type.__name__)
return self.manager
class EmptyManager(Manager):
def get_query_set(self):
return self.get_empty_query_set()

View File

@ -21,7 +21,8 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label', 'db_tablespace',
'abstract', 'managed', 'proxy', 'auto_created')
'abstract', 'managed', 'proxy', 'swappable', 'auto_created')
@python_2_unicode_compatible
class Options(object):
@ -32,8 +33,8 @@ class Options(object):
self.verbose_name_plural = None
self.db_table = ''
self.ordering = []
self.unique_together = []
self.permissions = []
self.unique_together = []
self.permissions = []
self.object_name, self.app_label = None, app_label
self.get_latest_by = None
self.order_with_respect_to = None
@ -55,6 +56,7 @@ class Options(object):
# in the end of the proxy_for_model chain. In particular, for
# concrete models, the concrete_model is always the class itself.
self.concrete_model = None
self.swappable = None
self.parents = SortedDict()
self.duplicate_targets = {}
self.auto_created = False
@ -218,6 +220,19 @@ class Options(object):
return raw
verbose_name_raw = property(verbose_name_raw)
def _swapped(self):
"""
Has this model been swapped out for another? If so, return the model
name of the replacement; otherwise, return None.
"""
if self.swappable:
model_label = '%s.%s' % (self.app_label, self.object_name)
swapped_for = getattr(settings, self.swappable, None)
if swapped_for not in (None, model_label):
return swapped_for
return None
swapped = property(_swapped)
def _fields(self):
"""
The getter for self.fields. This returns the list of field objects

View File

@ -5,5 +5,6 @@ Django Unit Test and Doctest framework.
from django.test.client import Client, RequestFactory
from django.test.testcases import (TestCase, TransactionTestCase,
SimpleTestCase, LiveServerTestCase, skipIfDBFeature,
skipUnlessDBFeature)
skipUnlessDBFeature
)
from django.test.utils import Approximate

View File

@ -44,6 +44,7 @@ from django.utils import unittest as ut2
from django.utils.encoding import force_text
from django.utils import six
from django.utils.unittest.util import safe_repr
from django.utils.unittest import skipIf
from django.views.static import serve
__all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase',
@ -53,6 +54,7 @@ normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
normalize_decimals = lambda s: re.sub(r"Decimal\('(\d+(\.\d*)?)'\)",
lambda m: "Decimal(\"%s\")" % m.groups()[0], s)
def to_list(value):
"""
Puts value into a list if it's not already one.

View File

@ -28,7 +28,7 @@ class Approximate(object):
def __eq__(self, other):
if self.val == other:
return True
return round(abs(self.val-other), self.places) == 0
return round(abs(self.val - other), self.places) == 0
class ContextList(list):
@ -46,7 +46,7 @@ class ContextList(list):
def __contains__(self, key):
try:
value = self[key]
self[key]
except KeyError:
return False
return True
@ -188,9 +188,11 @@ class override_settings(object):
if isinstance(test_func, type) and issubclass(test_func, TransactionTestCase):
original_pre_setup = test_func._pre_setup
original_post_teardown = test_func._post_teardown
def _pre_setup(innerself):
self.enable()
original_pre_setup(innerself)
def _post_teardown(innerself):
original_post_teardown(innerself)
self.disable()
@ -220,5 +222,6 @@ class override_settings(object):
setting_changed.send(sender=settings._wrapped.__class__,
setting=key, value=new_value)
def str_prefix(s):
return s % {'_': '' if six.PY3 else 'u'}

View File

@ -286,6 +286,9 @@ these changes.
* The ``mimetype`` argument to :class:`~django.http.HttpResponse` ``__init__``
will be removed (``content_type`` should be used instead).
* The ``AUTH_PROFILE_MODULE`` setting, and the ``get_profile()`` method on
the User model, will be removed.
2.0
---

View File

@ -110,15 +110,14 @@ A tuple of authentication backend classes (as strings) to use when attempting to
authenticate a user. See the :doc:`authentication backends documentation
</ref/authbackends>` for details.
.. setting:: AUTH_PROFILE_MODULE
.. setting:: AUTH_USER_MODEL
AUTH_PROFILE_MODULE
-------------------
AUTH_USER_MODEL
---------------
Default: Not defined
Default: 'auth.User'
The site-specific user profile model used by this site. See
:ref:`auth-profiles`.
The model to use to represent a User. See :ref:`auth-custom-user`.
.. setting:: CACHES
@ -2209,6 +2208,22 @@ ADMIN_MEDIA_PREFIX
integration. See the :doc:`Django 1.4 release notes</releases/1.4>` for
more information.
.. setting:: AUTH_PROFILE_MODULE
AUTH_PROFILE_MODULE
-------------------
.. deprecated:: 1.5
With the introduction of :ref:`custom User models <auth-custom-user>`,
the use of :setting:`AUTH_PROFILE_MODULE` to define a single profile
model is no longer supported. See the
:doc:`Django 1.5 release notes</releases/1.5>` for more information.
Default: Not defined
The site-specific user profile model used by this site. See
:ref:`auth-profiles`.
.. setting:: IGNORABLE_404_ENDS
IGNORABLE_404_ENDS

View File

@ -34,6 +34,23 @@ release featuring 2.7 support.
What's new in Django 1.5
========================
Configurable User model
~~~~~~~~~~~~~~~~~~~~~~~
In Django 1.5, you can now use your own model as the store for user-related
data. If your project needs a username with more than 30 characters, or if
you want to store usernames in a format other than first name/last name, or
you want to put custom profile information onto your User object, you can
now do so.
If you have a third-party reusable application that references the User model,
you may need to make some changes to the way you reference User instances. You
should also document any specific features of the User model that your
application relies upon.
See the :ref:`documentation on custom User models <auth-custom-user>` for
more details.
Support for saving a subset of model's fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -277,6 +294,18 @@ Session not saved on 500 responses
Django's session middleware will skip saving the session data if the
response's status code is 500.
Email checks on failed admin login
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Prior to Django 1.5, if you attempted to log into the admin interface and
mistakenly used your email address instead of your username, the admin
interface would provide a warning advising that your email address was
not your username. In Django 1.5, the introduction of
:ref:`custom User models <auth-custom-user>` has required the removal of this
warning. This doesn't change the login behavior of the admin site; it only
affects the warning message that is displayed under one particular mode of
login failure.
Changes in tests execution
~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -394,3 +423,16 @@ The markup contrib module has been deprecated and will follow an accelerated
deprecation schedule. Direct use of python markup libraries or 3rd party tag
libraries is preferred to Django maintaining this functionality in the
framework.
:setting:`AUTH_PROFILE_MODULE`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With the introduction of :ref:`custom User models <auth-custom-user>`, there is
no longer any need for a built-in mechanism to store user profile data.
You can still define user profiles models that have a one-to-one relation with
the User model - in fact, for many applications needing to associate data with
a User account, this will be an appropriate design pattern to follow. However,
the :setting:`AUTH_PROFILE_MODULE` setting, and the
:meth:`~django.contrib.auth.models.User.get_profile()` method for accessing
the user profile model, should not be used any longer.

View File

@ -250,6 +250,12 @@ Methods
.. method:: models.User.get_profile()
.. deprecated:: 1.5
With the introduction of :ref:`custom User models <auth-custom-user>`,
the use of :setting:`AUTH_PROFILE_MODULE` to define a single profile
model is no longer supported. See the
:doc:`Django 1.5 release notes</releases/1.5>` for more information.
Returns a site-specific profile for this user. Raises
:exc:`django.contrib.auth.models.SiteProfileNotAvailable` if the
current site doesn't allow profiles, or
@ -582,6 +588,12 @@ correct path and environment for you.
Storing additional information about users
------------------------------------------
.. deprecated:: 1.5
With the introduction of :ref:`custom User models <auth-custom-user>`,
the use of :setting:`AUTH_PROFILE_MODULE` to define a single profile
model is no longer supported. See the
:doc:`Django 1.5 release notes</releases/1.5>` for more information.
If you'd like to store additional information related to your users, Django
provides a method to specify a site-specific related model -- termed a "user
profile" -- for this purpose.
@ -1345,6 +1357,9 @@ Helper functions
URL to redirect to after log out. Overrides ``next`` if the given
``GET`` parameter is passed.
.. _built-in-auth-forms:
Built-in forms
--------------
@ -1735,6 +1750,350 @@ Fields
group.permissions.remove(permission, permission, ...)
group.permissions.clear()
.. _auth-custom-user:
Customizing the User model
==========================
.. versionadded:: 1.5
Some kinds of projects may have authentication requirements for which Django's
built-in :class:`~django.contrib.auth.models.User` model is not always
appropriate. For instance, on some sites it makes more sense to use an email
address as your identification token instead of a username.
Django allows you to override the default User model by providing a value for
the :setting:`AUTH_USER_MODEL` setting that references a custom model::
AUTH_USER_MODEL = 'myapp.MyUser'
This dotted pair describes the name of the Django app, and the name of the Django
model that you wish to use as your User model.
.. admonition:: Warning
Changing :setting:`AUTH_USER_MODEL` has a big effect on your database
structure. It changes the tables that are available, and it will affect the
construction of foreign keys and many-to-many relationships. If you intend
to set :setting:`AUTH_USER_MODEL`, you should set it before running
``manage.py syncdb`` for the first time.
If you have an existing project and you want to migrate to using a custom
User model, you may need to look into using a migration tool like South_
to ease the transition.
.. _South: http://south.aeracode.org
Referencing the User model
--------------------------
If you reference :class:`~django.contrib.auth.models.User` directly (for
example, by referring to it in a foreign key), your code will not work in
projects where the :setting:`AUTH_USER_MODEL` setting has been changed to a
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
: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::
from django.conf import settings
from django.db import models
class Article(models.Model)
author = models.ForeignKey(settings.AUTH_USER_MODEL)
Specifying a custom User model
------------------------------
.. admonition:: Model design considerations
Think carefully before handling information not directly related to
authentication in your custom User Model.
It may be better to store app-specific user information in a model
that has a relation with the User model. That allows each app to specify
its own user data requirements without risking conflicts with other
apps. On the other hand, queries to retrieve this related information
will involve a database join, which may have an effect on performance.
Django expects your custom User model to meet some minimum requirements.
1. Your model must have a single unique field that can be used for
identification purposes. This can be a username, an email address,
or any other unique attribute.
2. Your model must provide a way to address the user in a "short" and
"long" form. The most common interpretation of this would be to use
the user's given name as the "short" identifier, and the user's full
name as the "long" identifier. However, there are no constraints on
what these two methods return - if you want, they can return exactly
the same value.
The easiest way to construct a compliant custom User model is to inherit from
:class:`~django.contrib.auth.models.AbstractBaseUser`.
:class:`~django.contrib.auth.models.AbstractBaseUser` provides the core
implementation of a `User` model, including hashed passwords and tokenized
password resets. You must then provide some key implementation details:
.. attribute:: User.USERNAME_FIELD
A string describing the name of the field on the User model that is
used as the unique identifier. This will usually be a username of
some kind, but it can also be an email address, or any other unique
identifier. In the following example, the field `identifier` is used
as the identifying field::
class MyUser(AbstractBaseUser):
identfier = models.CharField(max_length=40, unique=True, db_index=True)
...
USERNAME_FIELD = 'identifier'
.. attribute:: User.REQUIRED_FIELDS
A list of the field names that *must* be provided when creating
a user. For example, here is the partial definition for a User model
that defines two required fields - a date of birth and height::
class MyUser(AbstractBaseUser):
...
date_of_birth = models.DateField()
height = models.FloatField()
...
REQUIRED_FIELDS = ['date_of_birth', 'height']
.. method:: User.get_full_name():
A longer formal identifier for the user. A common interpretation
would be the full name name of the user, but it can be any string that
identifies the user.
.. method:: User.get_short_name():
A short, informal identifier for the user. A common interpretation
would be the first name of the user, but it can be any string that
identifies the user in an informal way. It may also return the same
value as :meth:`django.contrib.auth.User.get_full_name()`.
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
:class:`~django.contrib.auth.models.UserManager`; however, if your User model
defines different fields, you will need to define a custom manager that
extends :class:`~django.contrib.auth.models.BaseUserManager` providing two
additional methods:
.. method:: UserManager.create_user(username, password=None, **other_fields)
The prototype of `create_user()` should accept all required fields
as arguments. For example, if your user model defines `username`,
and `date_of_birth` as required fields, then create_user should be
defined as::
def create_user(self, username, date_of_birth, password=None):
# create user here
.. method:: UserManager.create_superuser(username, password, **other_fields)
The prototype of `create_superuser()` should accept all required fields
as arguments. For example, if your user model defines `username`,
and `date_of_birth` as required fields, then create_user should be
defined as::
def create_superuser(self, username, date_of_birth, password):
# create superuser here
Unlike `create_user()`, `create_superuser()` *must* require the caller
to provider a password.
Extending Django's default User
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you're entirely happy with Django's :class:`~django.contrib.auth.models.User`
model and you just want to add some additional profile information, you can
simply subclass :class:`~django.contrib.auth.models.AbstractUser` and add your
custom profile fields.
Custom users and the built-in auth forms
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As you may expect, built-in Django's :ref:`forms <_built-in-auth-forms>`
and :ref:`views <other-built-in-views>` make certain assumptions about
the user model that they are working with.
If your user model doesn't follow the same assumptions, it may be necessary to define
a replacement form, and pass that form in as part of the configuration of the
auth views.
* :class:`~django.contrib.auth.forms.UserCreationForm`
Depends on the :class:`~django.contrib.auth.models.User` model.
Must be re-written for any custom user model.
* :class:`~django.contrib.auth.forms.UserChangeForm`
Depends on the :class:`~django.contrib.auth.models.User` model.
Must be re-written for any custom user model.
* :class:`~django.contrib.auth.forms.AuthenticationForm`
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`,
and will adapt to use the field defined in `USERNAME_FIELD`.
* :class:`~django.contrib.auth.forms.PasswordResetForm`
Assumes that the user model has an integer primary key, has a field named
`email` that can be used to identify the user, and a boolean field
named `is_active` to prevent password resets for inactive users.
* :class:`~django.contrib.auth.forms.SetPasswordForm`
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
* :class:`~django.contrib.auth.forms.PasswordChangeForm`
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
* :class:`~django.contrib.auth.forms.AdminPasswordChangeForm`
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`
Custom users and django.contrib.admin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want your custom User model to also work with Admin, your User model must
define some additional attributes and methods. These methods allow the admin to
control access of the User to admin content:
.. attribute:: User.is_staff
Returns True if the user is allowed to have access to the admin site.
.. attribute:: User.is_active
Returns True if the user account is currently active.
.. method:: User.has_perm(perm, obj=None):
Returns True if the user has the named permission. If `obj` is
provided, the permission needs to be checked against a specific object
instance.
.. method:: User.has_module_perms(app_label):
Returns True if the user has permission to access models in
the given app.
Custom users and Proxy models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One limitation of custom User models is that installing a custom User model
will break any proxy model extending :class:`~django.contrib.auth.models.User`.
Proxy models must be based on a concrete base class; by defining a custom User
model, you remove the ability of Django to reliably identify the base class.
If your project uses proxy models, you must either modify the proxy to extend
the User model that is currently in use in your project, or merge your proxy's
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.
This code would all live in a ``models.py`` file for a custom
authentication app::
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
class MyUserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=MyUserManager.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, date_of_birth, password):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
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 MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = MyUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['date_of_birth']
def get_full_name(self):
# The user is identified by their email address
return self.email
def get_short_name(self):
# The user is identified by their email address
return self.email
def __unicode__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
@property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
.. _authentication-backends:
Other authentication sources

View File

@ -21,11 +21,12 @@ class FieldErrors(models.Model):
decimalfield5 = models.DecimalField(max_digits=10, decimal_places=10)
filefield = models.FileField()
choices = models.CharField(max_length=10, choices='bad')
choices2 = models.CharField(max_length=10, choices=[(1,2,3),(1,2,3)])
choices2 = models.CharField(max_length=10, choices=[(1, 2, 3), (1, 2, 3)])
index = models.CharField(max_length=10, db_index='bad')
field_ = models.CharField(max_length=10)
nullbool = models.BooleanField(null=True)
class Target(models.Model):
tgt_safe = models.CharField(max_length=10)
clash1 = models.CharField(max_length=10)
@ -33,12 +34,14 @@ class Target(models.Model):
clash1_set = models.CharField(max_length=10)
class Clash1(models.Model):
src_safe = models.CharField(max_length=10)
foreign = models.ForeignKey(Target)
m2m = models.ManyToManyField(Target)
class Clash2(models.Model):
src_safe = models.CharField(max_length=10)
@ -48,6 +51,7 @@ class Clash2(models.Model):
m2m_1 = models.ManyToManyField(Target, related_name='id')
m2m_2 = models.ManyToManyField(Target, related_name='src_safe')
class Target2(models.Model):
clash3 = models.CharField(max_length=10)
foreign_tgt = models.ForeignKey(Target)
@ -56,6 +60,7 @@ class Target2(models.Model):
m2m_tgt = models.ManyToManyField(Target)
clashm2m_set = models.ManyToManyField(Target)
class Clash3(models.Model):
src_safe = models.CharField(max_length=10)
@ -65,12 +70,15 @@ class Clash3(models.Model):
m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt')
m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt')
class ClashForeign(models.Model):
foreign = models.ForeignKey(Target2)
class ClashM2M(models.Model):
m2m = models.ManyToManyField(Target2)
class SelfClashForeign(models.Model):
src_safe = models.CharField(max_length=10)
selfclashforeign = models.CharField(max_length=10)
@ -79,6 +87,7 @@ class SelfClashForeign(models.Model):
foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
class ValidM2M(models.Model):
src_safe = models.CharField(max_length=10)
validm2m = models.CharField(max_length=10)
@ -94,6 +103,7 @@ class ValidM2M(models.Model):
m2m_3 = models.ManyToManyField('self')
m2m_4 = models.ManyToManyField('self')
class SelfClashM2M(models.Model):
src_safe = models.CharField(max_length=10)
selfclashm2m = models.CharField(max_length=10)
@ -108,120 +118,148 @@ class SelfClashM2M(models.Model):
m2m_3 = models.ManyToManyField('self', symmetrical=False)
m2m_4 = models.ManyToManyField('self', symmetrical=False)
class Model(models.Model):
"But it's valid to call a model Model."
year = models.PositiveIntegerField() #1960
make = models.CharField(max_length=10) #Aston Martin
name = models.CharField(max_length=10) #DB 4 GT
year = models.PositiveIntegerField() # 1960
make = models.CharField(max_length=10) # Aston Martin
name = models.CharField(max_length=10) # DB 4 GT
class Car(models.Model):
colour = models.CharField(max_length=5)
model = models.ForeignKey(Model)
class MissingRelations(models.Model):
rel1 = models.ForeignKey("Rel1")
rel2 = models.ManyToManyField("Rel2")
class MissingManualM2MModel(models.Model):
name = models.CharField(max_length=5)
missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel")
class Person(models.Model):
name = models.CharField(max_length=5)
class Group(models.Model):
name = models.CharField(max_length=5)
primary = models.ManyToManyField(Person, through="Membership", related_name="primary")
secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary")
tertiary = models.ManyToManyField(Person, through="RelationshipDoubleFK", related_name="tertiary")
class GroupTwo(models.Model):
name = models.CharField(max_length=5)
primary = models.ManyToManyField(Person, through="Membership")
secondary = models.ManyToManyField(Group, through="MembershipMissingFK")
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
not_default_or_null = models.CharField(max_length=5)
class MembershipMissingFK(models.Model):
person = models.ForeignKey(Person)
class PersonSelfRefM2M(models.Model):
name = models.CharField(max_length=5)
friends = models.ManyToManyField('self', through="Relationship")
too_many_friends = models.ManyToManyField('self', through="RelationshipTripleFK")
class PersonSelfRefM2MExplicit(models.Model):
name = models.CharField(max_length=5)
friends = models.ManyToManyField('self', through="ExplicitRelationship", symmetrical=True)
class Relationship(models.Model):
first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set")
second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set")
date_added = models.DateTimeField()
class ExplicitRelationship(models.Model):
first = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_from_set")
second = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_to_set")
date_added = models.DateTimeField()
class RelationshipTripleFK(models.Model):
first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set_2")
second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set_2")
third = models.ForeignKey(PersonSelfRefM2M, related_name="too_many_by_far")
date_added = models.DateTimeField()
class RelationshipDoubleFK(models.Model):
first = models.ForeignKey(Person, related_name="first_related_name")
second = models.ForeignKey(Person, related_name="second_related_name")
third = models.ForeignKey(Group, related_name="rel_to_set")
date_added = models.DateTimeField()
class AbstractModel(models.Model):
name = models.CharField(max_length=10)
class Meta:
abstract = True
class AbstractRelationModel(models.Model):
fk1 = models.ForeignKey('AbstractModel')
fk2 = models.ManyToManyField('AbstractModel')
class UniqueM2M(models.Model):
""" Model to test for unique ManyToManyFields, which are invalid. """
unique_people = models.ManyToManyField(Person, unique=True)
class NonUniqueFKTarget1(models.Model):
""" Model to test for non-unique FK target in yet-to-be-defined model: expect an error """
tgt = models.ForeignKey('FKTarget', to_field='bad')
class UniqueFKTarget1(models.Model):
""" Model to test for unique FK target in yet-to-be-defined model: expect no error """
tgt = models.ForeignKey('FKTarget', to_field='good')
class FKTarget(models.Model):
bad = models.IntegerField()
good = models.IntegerField(unique=True)
class NonUniqueFKTarget2(models.Model):
""" Model to test for non-unique FK target in previously seen model: expect an error """
tgt = models.ForeignKey(FKTarget, to_field='bad')
class UniqueFKTarget2(models.Model):
""" Model to test for unique FK target in previously seen model: expect no error """
tgt = models.ForeignKey(FKTarget, to_field='good')
class NonExistingOrderingWithSingleUnderscore(models.Model):
class Meta:
ordering = ("does_not_exist",)
class InvalidSetNull(models.Model):
fk = models.ForeignKey('self', on_delete=models.SET_NULL)
class InvalidSetDefault(models.Model):
fk = models.ForeignKey('self', on_delete=models.SET_DEFAULT)
class UnicodeForeignKeys(models.Model):
"""Foreign keys which can translate to ascii should be OK, but fail if
they're not."""
@ -232,9 +270,11 @@ class UnicodeForeignKeys(models.Model):
# when adding the errors in core/management/validation.py
#bad = models.ForeignKey('★')
class PrimaryKeyNull(models.Model):
my_pk_field = models.IntegerField(primary_key=True, null=True)
class OrderByPKModel(models.Model):
"""
Model to test that ordering by pk passes validation.
@ -245,6 +285,62 @@ class OrderByPKModel(models.Model):
class Meta:
ordering = ('pk',)
class SwappableModel(models.Model):
"""A model that can be, but isn't swapped out.
References to this model *shoudln't* raise any validation error.
"""
name = models.CharField(max_length=100)
class Meta:
swappable = 'TEST_SWAPPABLE_MODEL'
class SwappedModel(models.Model):
"""A model that is swapped out.
References to this model *should* raise a validation error.
Requires TEST_SWAPPED_MODEL to be defined in the test environment;
this is guaranteed by the test runner using @override_settings.
"""
name = models.CharField(max_length=100)
class Meta:
swappable = 'TEST_SWAPPED_MODEL'
class BadSwappableValue(models.Model):
"""A model that can be swapped out; during testing, the swappable
value is not of the format app.model
"""
name = models.CharField(max_length=100)
class Meta:
swappable = 'TEST_SWAPPED_MODEL_BAD_VALUE'
class BadSwappableModel(models.Model):
"""A model that can be swapped out; during testing, the swappable
value references an unknown model.
"""
name = models.CharField(max_length=100)
class Meta:
swappable = 'TEST_SWAPPED_MODEL_BAD_MODEL'
class HardReferenceModel(models.Model):
fk_1 = models.ForeignKey(SwappableModel, related_name='fk_hardref1')
fk_2 = models.ForeignKey('invalid_models.SwappableModel', related_name='fk_hardref2')
fk_3 = models.ForeignKey(SwappedModel, related_name='fk_hardref3')
fk_4 = models.ForeignKey('invalid_models.SwappedModel', related_name='fk_hardref4')
m2m_1 = models.ManyToManyField(SwappableModel, related_name='m2m_hardref1')
m2m_2 = models.ManyToManyField('invalid_models.SwappableModel', related_name='m2m_hardref2')
m2m_3 = models.ManyToManyField(SwappedModel, related_name='m2m_hardref3')
m2m_4 = models.ManyToManyField('invalid_models.SwappedModel', related_name='m2m_hardref4')
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer.
invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer.
invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer.
@ -353,6 +449,12 @@ invalid_models.nonuniquefktarget2: Field 'bad' under model 'FKTarget' must have
invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist.
invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null.
invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value.
invalid_models.hardreferencemodel: 'fk_3' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
invalid_models.hardreferencemodel: 'fk_4' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
invalid_models.hardreferencemodel: 'm2m_3' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
invalid_models.hardreferencemodel: 'm2m_4' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
invalid_models.badswappablevalue: TEST_SWAPPED_MODEL_BAD_VALUE is not of the form 'app_label.app_name'.
invalid_models.badswappablemodel: Model has been swapped out for 'not_an_app.Target' which has not been installed or is abstract.
"""
if not connection.features.interprets_empty_strings_as_nulls:

View File

@ -4,6 +4,7 @@ import sys
from django.core.management.validation import get_validation_errors
from django.db.models.loading import cache, load_app
from django.test.utils import override_settings
from django.utils import unittest
from django.utils.six import StringIO
@ -31,14 +32,22 @@ class InvalidModelTestCase(unittest.TestCase):
cache._get_models_cache = {}
sys.stdout = self.old_stdout
# Technically, this isn't an override -- TEST_SWAPPED_MODEL must be
# set to *something* in order for the test to work. However, it's
# easier to set this up as an override than to require every developer
# to specify a value in their test settings.
@override_settings(
TEST_SWAPPED_MODEL='invalid_models.Target',
TEST_SWAPPED_MODEL_BAD_VALUE='not-a-model',
TEST_SWAPPED_MODEL_BAD_MODEL='not_an_app.Target',
)
def test_invalid_models(self):
try:
module = load_app("modeltests.invalid_models.invalid_models")
except Exception:
self.fail('Unable to load invalid model module')
count = get_validation_errors(self.stdout, module)
get_validation_errors(self.stdout, module)
self.stdout.seek(0)
error_log = self.stdout.read()
actual = error_log.split('\n')

View File

@ -1,10 +1,13 @@
from __future__ import absolute_import, unicode_literals
import copy
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core import management
from django.core.exceptions import FieldError
from django.db import models, DEFAULT_DB_ALIAS
from django.db.models import signals
from django.db.models.loading import cache
from django.test import TestCase
@ -13,6 +16,7 @@ from .models import (MyPerson, Person, StatusPerson, LowerStatusPerson,
Country, State, StateProxy, TrackerUser, BaseUser, Bug, ProxyTrackerUser,
Improvement, ProxyProxyBug, ProxyBug, ProxyImprovement)
class ProxyModelTests(TestCase):
def test_same_manager_queries(self):
"""
@ -91,7 +95,7 @@ class ProxyModelTests(TestCase):
)
self.assertRaises(Person.MultipleObjectsReturned,
MyPersonProxy.objects.get,
id__lt=max_id+1
id__lt=max_id + 1
)
self.assertRaises(Person.DoesNotExist,
StatusPerson.objects.get,
@ -104,7 +108,7 @@ class ProxyModelTests(TestCase):
self.assertRaises(Person.MultipleObjectsReturned,
StatusPerson.objects.get,
id__lt=max_id+1
id__lt=max_id + 1
)
def test_abc(self):
@ -138,10 +142,40 @@ class ProxyModelTests(TestCase):
def build_new_fields():
class NoNewFields(Person):
newfield = models.BooleanField()
class Meta:
proxy = True
self.assertRaises(FieldError, build_new_fields)
def test_swappable(self):
try:
# This test adds dummy applications to the app cache. These
# need to be removed in order to prevent bad interactions
# with the flush operation in other tests.
old_app_models = copy.deepcopy(cache.app_models)
old_app_store = copy.deepcopy(cache.app_store)
settings.TEST_SWAPPABLE_MODEL = 'proxy_models.AlternateModel'
class SwappableModel(models.Model):
class Meta:
swappable = 'TEST_SWAPPABLE_MODEL'
class AlternateModel(models.Model):
pass
# You can't proxy a swapped model
with self.assertRaises(TypeError):
class ProxyModel(SwappableModel):
class Meta:
proxy = True
finally:
del settings.TEST_SWAPPABLE_MODEL
cache.app_models = old_app_models
cache.app_store = old_app_store
def test_myperson_manager(self):
Person.objects.create(name="fred")
Person.objects.create(name="wilma")

View File

@ -52,6 +52,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
ERROR_MESSAGE = "Please enter the correct username and password \
for a staff account. Note that both fields are case-sensitive."
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminViewBasicTest(TestCase):
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml',
@ -141,7 +142,7 @@ class AdminViewBasicTest(TestCase):
"article_set-MAX_NUM_FORMS": "0",
}
response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
self.assertEqual(response.status_code, 302) # redirect somewhere
self.assertEqual(response.status_code, 302) # redirect somewhere
def testPopupAddPost(self):
"""
@ -205,7 +206,7 @@ class AdminViewBasicTest(TestCase):
A smoke test to ensure POST on edit_view works.
"""
response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, self.inline_post_data)
self.assertEqual(response.status_code, 302) # redirect somewhere
self.assertEqual(response.status_code, 302) # redirect somewhere
def testEditSaveAs(self):
"""
@ -221,7 +222,7 @@ class AdminViewBasicTest(TestCase):
"article_set-5-section": "1",
})
response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, post_data)
self.assertEqual(response.status_code, 302) # redirect somewhere
self.assertEqual(response.status_code, 302) # redirect somewhere
def testChangeListSortingCallable(self):
"""
@ -308,7 +309,7 @@ class AdminViewBasicTest(TestCase):
self.assertContentBefore(response, link2, link1)
# Test we can override with query string
response = self.client.get('/test_admin/admin/admin_views/language/', {'o':'-1'})
response = self.client.get('/test_admin/admin/admin_views/language/', {'o': '-1'})
self.assertContentBefore(response, link1, link2)
def testChangeListSortingOverrideModelAdmin(self):
@ -358,13 +359,13 @@ class AdminViewBasicTest(TestCase):
kinds of 'ordering' fields: field names, method on the model
admin and model itself, and other callables. See #17252.
"""
models = [(AdminOrderedField, 'adminorderedfield' ),
models = [(AdminOrderedField, 'adminorderedfield'),
(AdminOrderedModelMethod, 'adminorderedmodelmethod'),
(AdminOrderedAdminMethod, 'adminorderedadminmethod'),
(AdminOrderedCallable, 'adminorderedcallable' )]
(AdminOrderedCallable, 'adminorderedcallable')]
for model, url in models:
a1 = model.objects.create(stuff='The Last Item', order=3)
a2 = model.objects.create(stuff='The First Item', order=1)
a1 = model.objects.create(stuff='The Last Item', order=3)
a2 = model.objects.create(stuff='The First Item', order=1)
a3 = model.objects.create(stuff='The Middle Item', order=2)
response = self.client.get('/test_admin/admin/admin_views/%s/' % url, {})
self.assertEqual(response.status_code, 200)
@ -671,7 +672,6 @@ class AdminJavaScriptTest(TestCase):
'<script type="text/javascript">document.getElementById("id_start_date_0").focus();</script>'
)
def test_js_minified_only_if_debug_is_false(self):
"""
Ensure that the minified versions of the JS files are only used when
@ -709,7 +709,7 @@ class AdminJavaScriptTest(TestCase):
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class SaveAsTests(TestCase):
urls = "regressiontests.admin_views.urls"
fixtures = ['admin-views-users.xml','admin-views-person.xml']
fixtures = ['admin-views-users.xml', 'admin-views-person.xml']
def setUp(self):
self.client.login(username='super', password='secret')
@ -719,7 +719,7 @@ class SaveAsTests(TestCase):
def test_save_as_duplication(self):
"""Ensure save as actually creates a new person"""
post_data = {'_saveasnew':'', 'name':'John M', 'gender':1, 'age': 42}
post_data = {'_saveasnew': '', 'name': 'John M', 'gender': 1, 'age': 42}
response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
self.assertEqual(len(Person.objects.filter(name='John M')), 1)
self.assertEqual(len(Person.objects.filter(id=1)), 1)
@ -732,10 +732,11 @@ class SaveAsTests(TestCase):
"""
response = self.client.get('/test_admin/admin/admin_views/person/1/')
self.assertTrue(response.context['save_as'])
post_data = {'_saveasnew':'', 'name':'John M', 'gender':3, 'alive':'checked'}
post_data = {'_saveasnew': '', 'name': 'John M', 'gender': 3, 'alive': 'checked'}
response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
self.assertEqual(response.context['form_url'], '/test_admin/admin/admin_views/person/add/')
class CustomModelAdminTest(AdminViewBasicTest):
urls = "regressiontests.admin_views.urls"
urlbit = "admin2"
@ -791,11 +792,13 @@ class CustomModelAdminTest(AdminViewBasicTest):
response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
self.assertEqual(response.content, b"Django is a magical pony!")
def get_perm(Model, perm):
"""Return the permission object, for the Model"""
ct = ContentType.objects.get_for_model(Model)
return Permission.objects.get(content_type=ct, codename=perm)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminViewPermissionsTest(TestCase):
"""Tests for Admin Views Permissions."""
@ -898,7 +901,7 @@ class AdminViewPermissionsTest(TestCase):
response = self.client.get('/test_admin/admin/')
self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/', self.super_email_login)
self.assertContains(login, "Your e-mail address is not your username")
self.assertContains(login, ERROR_MESSAGE)
# only correct passwords get a username hint
login = self.client.post('/test_admin/admin/', self.super_email_bad_login)
self.assertContains(login, ERROR_MESSAGE)
@ -959,7 +962,7 @@ class AdminViewPermissionsTest(TestCase):
def testAddView(self):
"""Test add view restricts access and actually adds items."""
add_dict = {'title' : 'Døm ikke',
add_dict = {'title': 'Døm ikke',
'content': '<p>great article</p>',
'date_0': '2008-03-18', 'date_1': '10:54:39',
'section': 1}
@ -1014,7 +1017,7 @@ class AdminViewPermissionsTest(TestCase):
def testChangeView(self):
"""Change view should restrict access and allow users to edit items."""
change_dict = {'title' : 'Ikke fordømt',
change_dict = {'title': 'Ikke fordømt',
'content': '<p>edited article</p>',
'date_0': '2008-03-18', 'date_1': '10:54:39',
'section': 1}
@ -1346,6 +1349,7 @@ class AdminViewDeletedObjectsTest(TestCase):
response = self.client.get('/test_admin/admin/admin_views/plot/%s/delete/' % quote(3))
self.assertContains(response, should_contain)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminViewStringPrimaryKeyTest(TestCase):
urls = "regressiontests.admin_views.urls"
@ -1400,7 +1404,7 @@ class AdminViewStringPrimaryKeyTest(TestCase):
response = self.client.get('/test_admin/admin/')
should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (escape(quote(self.pk)), escape(self.pk))
self.assertContains(response, should_contain)
should_contain = "Model with string primary key" # capitalized in Recent Actions
should_contain = "Model with string primary key" # capitalized in Recent Actions
self.assertContains(response, should_contain)
logentry = LogEntry.objects.get(content_type__name__iexact=should_contain)
# http://code.djangoproject.com/ticket/10275
@ -1522,7 +1526,7 @@ class SecureViewTests(TestCase):
def test_secure_view_shows_login_if_not_logged_in(self):
"Ensure that we see the login form"
response = self.client.get('/test_admin/admin/secure-view/' )
response = self.client.get('/test_admin/admin/secure-view/')
self.assertTemplateUsed(response, 'admin/login.html')
def test_secure_view_login_successfully_redirects_to_original_url(self):
@ -1556,7 +1560,7 @@ class SecureViewTests(TestCase):
response = self.client.get('/test_admin/admin/secure-view/')
self.assertEqual(response.status_code, 200)
login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login)
self.assertContains(login, "Your e-mail address is not your username")
self.assertContains(login, ERROR_MESSAGE)
# only correct passwords get a username hint
login = self.client.post('/test_admin/admin/secure-view/', self.super_email_bad_login)
self.assertContains(login, ERROR_MESSAGE)
@ -1626,6 +1630,7 @@ class SecureViewTests(TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], 'http://example.com/users/super/')
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminViewUnicodeTest(TestCase):
urls = "regressiontests.admin_views.urls"
@ -1668,7 +1673,7 @@ class AdminViewUnicodeTest(TestCase):
}
response = self.client.post('/test_admin/admin/admin_views/book/1/', post_data)
self.assertEqual(response.status_code, 302) # redirect somewhere
self.assertEqual(response.status_code, 302) # redirect somewhere
def testUnicodeDelete(self):
"""
@ -2035,7 +2040,7 @@ class AdminViewListEditable(TestCase):
story1 = Story.objects.create(title='The adventures of Guido', content='Once upon a time in Djangoland...')
story2 = Story.objects.create(title='Crouching Tiger, Hidden Python', content='The Python was sneaking into...')
response = self.client.get('/test_admin/admin/admin_views/story/')
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
self.assertContains(response, 'id="id_form-1-id"', 1)
self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id), html=True)
self.assertContains(response, '<td>%d</td>' % story1.id, 1)
@ -2051,7 +2056,7 @@ class AdminViewListEditable(TestCase):
link1 = reverse('admin:admin_views_otherstory_change', args=(story1.pk,))
link2 = reverse('admin:admin_views_otherstory_change', args=(story2.pk,))
response = self.client.get('/test_admin/admin/admin_views/otherstory/')
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
self.assertContains(response, 'id="id_form-1-id"', 1)
self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id), html=True)
self.assertContains(response, '<th><a href="%s">%d</a></th>' % (link1, story1.id), 1)
@ -2109,7 +2114,7 @@ class AdminSearchTest(TestCase):
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminInheritedInlinesTest(TestCase):
urls = "regressiontests.admin_views.urls"
fixtures = ['admin-views-users.xml',]
fixtures = ['admin-views-users.xml']
def setUp(self):
self.client.login(username='super', password='secret')
@ -2146,7 +2151,7 @@ class AdminInheritedInlinesTest(TestCase):
}
response = self.client.post('/test_admin/admin/admin_views/persona/add/', post_data)
self.assertEqual(response.status_code, 302) # redirect somewhere
self.assertEqual(response.status_code, 302) # redirect somewhere
self.assertEqual(Persona.objects.count(), 1)
self.assertEqual(FooAccount.objects.count(), 1)
self.assertEqual(BarAccount.objects.count(), 1)
@ -2193,6 +2198,7 @@ class AdminInheritedInlinesTest(TestCase):
self.assertEqual(BarAccount.objects.all()[0].username, "%s-1" % bar_user)
self.assertEqual(Persona.objects.all()[0].accounts.count(), 2)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminActionsTest(TestCase):
urls = "regressiontests.admin_views.urls"
@ -2208,7 +2214,7 @@ class AdminActionsTest(TestCase):
"Tests a custom action defined in a ModelAdmin method"
action_data = {
ACTION_CHECKBOX_NAME: [1],
'action' : 'mail_admin',
'action': 'mail_admin',
'index': 0,
}
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
@ -2219,12 +2225,12 @@ class AdminActionsTest(TestCase):
"Tests the default delete action defined as a ModelAdmin method"
action_data = {
ACTION_CHECKBOX_NAME: [1, 2],
'action' : 'delete_selected',
'action': 'delete_selected',
'index': 0,
}
delete_confirmation_data = {
ACTION_CHECKBOX_NAME: [1, 2],
'action' : 'delete_selected',
'action': 'delete_selected',
'post': 'yes',
}
confirmation = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
@ -2248,12 +2254,12 @@ class AdminActionsTest(TestCase):
subscriber.save()
action_data = {
ACTION_CHECKBOX_NAME: [9999, 2],
'action' : 'delete_selected',
'action': 'delete_selected',
'index': 0,
}
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
self.assertTemplateUsed(response, 'admin/delete_selected_confirmation.html')
self.assertContains(response, 'value="9999"') # Instead of 9,999
self.assertContains(response, 'value="9999"') # Instead of 9,999
self.assertContains(response, 'value="2"')
settings.USE_THOUSAND_SEPARATOR = self.old_USE_THOUSAND_SEPARATOR
settings.USE_L10N = self.old_USE_L10N
@ -2270,7 +2276,7 @@ class AdminActionsTest(TestCase):
action_data = {
ACTION_CHECKBOX_NAME: [q1.pk, q2.pk],
'action' : 'delete_selected',
'action': 'delete_selected',
'index': 0,
}
@ -2284,7 +2290,7 @@ class AdminActionsTest(TestCase):
"Tests a custom action defined in a function"
action_data = {
ACTION_CHECKBOX_NAME: [1],
'action' : 'external_mail',
'action': 'external_mail',
'index': 0,
}
response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
@ -2295,7 +2301,7 @@ class AdminActionsTest(TestCase):
"Tests a custom action defined in a function"
action_data = {
ACTION_CHECKBOX_NAME: [1],
'action' : 'redirect_to',
'action': 'redirect_to',
'index': 0,
}
response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
@ -2309,7 +2315,7 @@ class AdminActionsTest(TestCase):
"""
action_data = {
ACTION_CHECKBOX_NAME: [1],
'action' : 'external_mail',
'action': 'external_mail',
'index': 0,
}
url = '/test_admin/admin/admin_views/externalsubscriber/?o=1'
@ -2374,7 +2380,7 @@ class AdminActionsTest(TestCase):
"""
action_data = {
ACTION_CHECKBOX_NAME: [],
'action' : 'delete_selected',
'action': 'delete_selected',
'index': 0,
}
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
@ -2388,7 +2394,7 @@ class AdminActionsTest(TestCase):
"""
action_data = {
ACTION_CHECKBOX_NAME: [1, 2],
'action' : '',
'action': '',
'index': 0,
}
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
@ -2432,7 +2438,7 @@ class TestCustomChangeList(TestCase):
# Insert some data
post_data = {"name": "First Gadget"}
response = self.client.post('/test_admin/%s/admin_views/gadget/add/' % self.urlbit, post_data)
self.assertEqual(response.status_code, 302) # redirect somewhere
self.assertEqual(response.status_code, 302) # redirect somewhere
# Hit the page once to get messages out of the queue message list
response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit)
# Ensure that that data is still not visible on the page
@ -2460,6 +2466,7 @@ class TestInlineNotEditable(TestCase):
response = self.client.get('/test_admin/admin/admin_views/parent/add/')
self.assertEqual(response.status_code, 200)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminCustomQuerysetTest(TestCase):
urls = "regressiontests.admin_views.urls"
@ -2516,6 +2523,7 @@ class AdminCustomQuerysetTest(TestCase):
# Message should contain non-ugly model name. Instance representation is set by model's __unicode__()
self.assertContains(response, '<li class="info">The cover letter &quot;John Doe II&quot; was changed successfully.</li>', html=True)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminInlineFileUploadTest(TestCase):
urls = "regressiontests.admin_views.urls"
@ -2656,7 +2664,7 @@ class AdminInlineTests(TestCase):
result = self.client.login(username='super', password='secret')
self.assertEqual(result, True)
self.collector = Collector(pk=1,name='John Fowles')
self.collector = Collector(pk=1, name='John Fowles')
self.collector.save()
def tearDown(self):
@ -2982,14 +2990,14 @@ class PrePopulatedTest(TestCase):
self.assertNotContains(response, "field['dependency_ids'].push('#id_title');")
self.assertNotContains(response, "id: '#id_prepopulatedsubpost_set-0-subslug',")
@override_settings(USE_THOUSAND_SEPARATOR = True, USE_L10N = True)
@override_settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=True)
def test_prepopulated_maxlength_localized(self):
"""
Regression test for #15938: if USE_THOUSAND_SEPARATOR is set, make sure
that maxLength (in the JavaScript) is rendered without separators.
"""
response = self.client.get('/test_admin/admin/admin_views/prepopulatedpostlargeslug/add/')
self.assertContains(response, "maxLength: 1000") # instead of 1,000
self.assertContains(response, "maxLength: 1000") # instead of 1,000
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@ -3035,8 +3043,8 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase):
self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-name').send_keys(' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog text... ')
slug1 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-slug1').get_attribute('value')
slug2 = self.selenium.find_element_by_css_selector('#id_relatedprepopulated_set-1-slug2').get_attribute('value')
self.assertEqual(slug1, 'now-you-have-another-stacked-inline-very-loooooooo') # 50 characters maximum for slug1 field
self.assertEqual(slug2, 'option-two-now-you-have-another-stacked-inline-very-looooooo') # 60 characters maximum for slug2 field
self.assertEqual(slug1, 'now-you-have-another-stacked-inline-very-loooooooo') # 50 characters maximum for slug1 field
self.assertEqual(slug2, 'option-two-now-you-have-another-stacked-inline-very-looooooo') # 60 characters maximum for slug2 field
# Tabular inlines ----------------------------------------------------
# Initial inline
@ -3087,7 +3095,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase):
slug2='option-one-here-stacked-inline',
)
RelatedPrepopulated.objects.get(
name=' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooo', # 75 characters in name field
name=' now you haVe anöther sŤāÇkeð inline with a very ... loooooooooooooooooo', # 75 characters in name field
pubdate='1999-01-25',
status='option two',
slug1='now-you-have-another-stacked-inline-very-loooooooo',
@ -3112,6 +3120,7 @@ class SeleniumPrePopulatedFirefoxTests(AdminSeleniumWebDriverTestCase):
class SeleniumPrePopulatedChromeTests(SeleniumPrePopulatedFirefoxTests):
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
class SeleniumPrePopulatedIETests(SeleniumPrePopulatedFirefoxTests):
webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'
@ -3172,7 +3181,7 @@ class ReadonlyTest(TestCase):
p = Post.objects.get()
self.assertEqual(p.posted, datetime.date.today())
data["posted"] = "10-8-1990" # some date that's not today
data["posted"] = "10-8-1990" # some date that's not today
response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
self.assertEqual(response.status_code, 302)
self.assertEqual(Post.objects.count(), 2)
@ -3214,7 +3223,7 @@ class RawIdFieldsTest(TestCase):
response = self.client.get('/test_admin/admin/admin_views/sketch/add/')
# Find the link
m = re.search(br'<a href="([^"]*)"[^>]* id="lookup_id_inquisition"', response.content)
self.assertTrue(m) # Got a match
self.assertTrue(m) # Got a match
popup_url = m.groups()[0].decode().replace("&amp;", "&")
# Handle relative links
@ -3224,6 +3233,7 @@ class RawIdFieldsTest(TestCase):
self.assertContains(response2, "Spain")
self.assertNotContains(response2, "England")
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class UserAdminTest(TestCase):
"""
@ -3378,7 +3388,7 @@ class CSSTest(TestCase):
self.assertContains(response, 'class="form-row field-awesomeness_level"')
self.assertContains(response, 'class="form-row field-coolness"')
self.assertContains(response, 'class="form-row field-value"')
self.assertContains(response, 'class="form-row"') # The lambda function
self.assertContains(response, 'class="form-row"') # The lambda function
# The tabular inline
self.assertContains(response, '<td class="field-url">')
@ -3390,6 +3400,7 @@ try:
except ImportError:
docutils = None
@unittest.skipUnless(docutils, "no docutils installed.")
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminDocsTest(TestCase):
@ -3448,7 +3459,7 @@ class ValidXHTMLTests(TestCase):
@override_settings(
TEMPLATE_CONTEXT_PROCESSORS=filter(
lambda t:t!='django.core.context_processors.i18n',
lambda t: t != 'django.core.context_processors.i18n',
global_settings.TEMPLATE_CONTEXT_PROCESSORS),
USE_I18N=False,
)
@ -3585,6 +3596,7 @@ class DateHierarchyTests(TestCase):
self.assert_non_localized_year(response, 2003)
self.assert_non_localized_year(response, 2005)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminCustomSaveRelatedTests(TestCase):
"""