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: commitd84749a0f0
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 commit531e7715da
Merge:29d1abb
1f84b04
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Wed Sep 26 07:09:23 2012 +0800 Merged recent trunk changes. commit29d1abbe35
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 commit8a527dda13
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. commite2b6e22f29
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. commit98aba856b5
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() commit0229209c84
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. commit6494bf91f2
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 17 21:38:44 2012 +0800 Improved validation of swappable model settings. commit5a04cde342
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Mon Sep 17 07:15:14 2012 +0800 Removed some unused imports. commitffd535e413
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 commit913e1ac84c
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. commit280bf19e94
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 commitdbb3900775
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 18:09:27 2012 +0800 Fixes for Python 3 compatibility. commitdfd72131d8
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 15:54:30 2012 +0800 Added protection against proxying swapped models. commitabcb027190
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 15:11:10 2012 +0800 Cleanup and documentation of AbstractUser base class. commita9491a8776
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 commitfd8bb4e3e4
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 14:20:14 2012 +0800 Documentation improvements coming from community review. commitb550a6d06d
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 13:52:47 2012 +0800 Refactored skipIfCustomUser into the contrib.auth tests. commit52a02f1110
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sun Sep 16 13:46:10 2012 +0800 Refactored common 'get' pattern into manager method. commitb441a6bbc7
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. commit08bcb4aec1
Author: Anssi Kääriäinen <akaariai@gmail.com> Date: Sat Sep 15 18:30:33 2012 +0300 Splitted User to AbstractUser and User commitd9f5e5addb
Author: Anssi Kääriäinen <akaariai@gmail.com> Date: Sat Sep 15 18:30:02 2012 +0300 Reworked REQUIRED_FIELDS + create_user() interaction commit579f152e4a
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 commit918497218c
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 20:18:19 2012 +0800 Deprecate AUTH_PROFILE_MODULE and get_profile(). commit334cdfc1bb
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. commit5d7bb22e8d
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Sat Sep 15 19:59:49 2012 +0800 Ensure swapped models can't be queried. commit57ac6e3d32
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 commitf2ec915b20
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 commit19526563b5
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. commit2c5e833a30
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. commit20d1892491
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 commit40ea8b8882
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. commite6aaf65970
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. commit75118bd242
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. commitd088b3af58
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 commit7e82e83d67
Merge:e29c010
39aa890
Author: Russell Keith-Magee <russell@keith-magee.com> Date: Fri Sep 7 23:45:03 2012 +0800 Merged master changes. commite29c010beb
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 commit8e3fd703d0
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. commit507bb50a92
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. commitdabe362836
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. commit7cc0baf89d
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:
parent
7c11b1a470
commit
70a0de37d1
|
@ -488,6 +488,8 @@ PROFANITIES_LIST = ()
|
|||
# AUTHENTICATION #
|
||||
##################
|
||||
|
||||
AUTH_USER_MODEL = 'auth.User'
|
||||
|
||||
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
|
||||
|
||||
LOGIN_URL = '/accounts/login/'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)."
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = "Validates all installed models."
|
||||
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 "John Doe II" 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("&", "&")
|
||||
|
||||
# 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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue