Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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

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

Squashed commit of the following:

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

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

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

    Merged recent trunk changes.

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

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

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

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

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

    Modifications to the handling and docs for auth forms.

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

    Improved error handling and docs for get_user_model()

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

    Merged recent Django trunk changes.

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

    Improved validation of swappable model settings.

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

    Removed some unused imports.

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

    Corrected attribute access on for get_by_natural_key

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

    Added test for proxy model safeguards on swappable models.

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

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

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

    Fixes for Python 3 compatibility.

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

    Added protection against proxying swapped models.

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

    Cleanup and documentation of AbstractUser base class.

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

    Merge commit '08bcb4aec1ed154cefc631b8510ee13e9af0c19d' into t3011

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

    Documentation improvements coming from community review.

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

    Refactored skipIfCustomUser into the contrib.auth tests.

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

    Refactored common 'get' pattern into manager method.

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

    Added note about backwards incompatible change to admin login messages.

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

    Splitted User to AbstractUser and User

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

    Reworked REQUIRED_FIELDS + create_user() interaction

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

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

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

    Deprecate AUTH_PROFILE_MODULE and get_profile().

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

    Added release notes for new swappable User feature.

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

    Ensure swapped models can't be queried.

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

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

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

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

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

    Merge recent changes from master.

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

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

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

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

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

    Added documentation for REQUIRED_FIELDS in custom auth.

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

    Added first draft of custom User docs.

    Thanks to Greg Turner for the initial text.

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

    Admin app should not allow username discovery

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

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

    Admin app login form should use swapped user model

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

    Merged master changes.

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

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

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

    Merged recent changes from trunk.

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

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

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

    Modified auth management commands to handle custom user definitions.

commit 7cc0baf
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
  • Loading branch information...
commit 70a0de37d132e5f1514fb939875f69649f103124 1 parent 7c11b1a
Russell Keith-Magee freakboy3742 authored
Showing with 1,412 additions and 343 deletions.
  1. +2 −0  django/conf/global_settings.py
  2. +2 −13 django/contrib/admin/forms.py
  3. +4 −2 django/contrib/admin/models.py
  4. +20 −14 django/contrib/admin/sites.py
  5. +1 −1  django/contrib/admin/templates/admin/base.html
  6. +1 −1  django/contrib/admin/templates/admin/login.html
  7. +1 −0  django/contrib/admin/views/decorators.py
  8. +22 −1 django/contrib/auth/__init__.py
  9. +15 −9 django/contrib/auth/backends.py
  10. +14 −0 django/contrib/auth/fixtures/custom_user.json
  11. +11 −4 django/contrib/auth/forms.py
  12. +21 −11 django/contrib/auth/management/__init__.py
  13. +10 −6 django/contrib/auth/management/commands/changepassword.py
  14. +63 −59 django/contrib/auth/management/commands/createsuperuser.py
  15. +107 −66 django/contrib/auth/models.py
  16. +13 −24 django/contrib/auth/tests/__init__.py
  17. +5 −0 django/contrib/auth/tests/auth_backends.py
  18. +38 −7 django/contrib/auth/tests/basic.py
  19. +2 −1  django/contrib/auth/tests/context_processors.py
  20. +75 −0 django/contrib/auth/tests/custom_user.py
  21. +3 −1 django/contrib/auth/tests/decorators.py
  22. +7 −1 django/contrib/auth/tests/forms.py
  23. +99 −1 django/contrib/auth/tests/management.py
  24. +6 −0 django/contrib/auth/tests/models.py
  25. +4 −0 django/contrib/auth/tests/remote_user.py
  26. +2 −0  django/contrib/auth/tests/signals.py
  27. +2 −0  django/contrib/auth/tests/tokens.py
  28. +9 −0 django/contrib/auth/tests/utils.py
  29. +30 −0 django/contrib/auth/tests/views.py
  30. +1 −0  django/contrib/auth/tokens.py
  31. +14 −5 django/contrib/auth/views.py
  32. +11 −6 django/contrib/comments/models.py
  33. +12 −1 django/core/exceptions.py
  34. +1 −0  django/core/management/commands/sqlall.py
  35. +1 −1  django/core/management/commands/syncdb.py
  36. +1 −0  django/core/management/commands/validate.py
  37. +10 −2 django/core/management/sql.py
  38. +24 −3 django/core/management/validation.py
  39. +21 −12 django/core/validators.py
  40. +8 −2 django/db/backends/__init__.py
  41. +8 −8 django/db/backends/creation.py
  42. +14 −6 django/db/models/base.py
  43. +27 −8 django/db/models/fields/related.py
  44. +1 −0  django/db/models/loading.py
  45. +7 −2 django/db/models/manager.py
  46. +18 −3 django/db/models/options.py
  47. +2 −1  django/test/__init__.py
  48. +2 −0  django/test/testcases.py
  49. +5 −2 django/test/utils.py
  50. +3 −0  docs/internals/deprecation.txt
  51. +21 −6 docs/ref/settings.txt
  52. +42 −0 docs/releases/1.5.txt
  53. +359 −0 docs/topics/auth.txt
  54. +106 −4 tests/modeltests/invalid_models/invalid_models/models.py
  55. +11 −2 tests/modeltests/invalid_models/tests.py
  56. +36 −2 tests/modeltests/proxy_models/tests.py
  57. +57 −45 tests/regressiontests/admin_views/tests.py
2  django/conf/global_settings.py
View
@@ -488,6 +488,8 @@
# AUTHENTICATION #
##################
+AUTH_USER_MODEL = 'auth.User'
+
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
LOGIN_URL = '/accounts/login/'
15 django/contrib/admin/forms.py
View
@@ -4,12 +4,12 @@
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 @@ def clean(self):
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)
6 django/contrib/admin/models.py
View
@@ -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 @@
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)
34 django/contrib/admin/sites.py
View
@@ -9,7 +9,6 @@
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 @@
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 @@ def register(self, model_or_iterable, admin_class=None, **options):
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 @@ def login(self, request, extra_context=None):
REDIRECT_FIELD_NAME: request.get_full_path(),
}
context.update(extra_context or {})
+
defaults = {
'extra_context': context,
'current_app': self.name,
2  django/contrib/admin/templates/admin/base.html
View
@@ -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 %}
2  django/contrib/admin/templates/admin/login.html
View
@@ -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 %}
1  django/contrib/admin/views/decorators.py
View
@@ -4,6 +4,7 @@
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
23 django/contrib/auth/__init__.py
View
@@ -6,9 +6,10 @@
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:
24 django/contrib/auth/backends.py
View
@@ -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 @@ def has_module_perms(self, user_obj, app_label):
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 @@ def authenticate(self, remote_user):
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
14 django/contrib/auth/fixtures/custom_user.json
View
@@ -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"
+ }
+ }
+]
15 django/contrib/auth/forms.py
View
@@ -7,9 +7,10 @@
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 @@ def __init__(self, request=None, *args, **kwargs):
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 @@ def clean_email(self):
"""
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)
32 django/contrib/auth/management/__init__.py
View
@@ -6,9 +6,10 @@
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")
16 django/contrib/auth/management/commands/changepassword.py
View
@@ -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 @@ def handle(self, *args, **options):
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 @@ def handle(self, *args, **options):
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
122 django/contrib/auth/management/commands/createsuperuser.py
View
@@ -3,109 +3,114 @@
"""
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(
Jannis Leidel Owner
jezdez added a note

Am I missing something obvious that requires this to be a tuple?

Marc Tamlyn Collaborator

The thing you're adding it to is a tuple and you can't concatenate a tuple and a generator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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 @@ def handle(self, *args, **options):
self.stderr.write("Error: Blank passwords aren't allowed.")
password = None
continue
Jaap Roes
jaap3 added a note

With the break gone, this continue has no purpose

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
- 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.")
173 django/contrib/auth/models.py
View
@@ -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 @@ def natural_key(self):
return (self.name,)
-class UserManager(models.Manager):
+class BaseUserManager(models.Manager):
@classmethod
def normalize_email(cls, email):
@@ -148,7 +152,25 @@ def normalize_email(cls, email):
email = '@'.join([email_name, domain_part.lower()])
return email
- def create_user(self, username, email=None, password=None):
+ def make_random_password(self, length=10,
+ allowed_chars='abcdefghjkmnpqrstuvwxyz'
+ 'ABCDEFGHJKLMNPQRSTUVWXYZ'
+ '23456789'):
+ """
+ Generates a random password with the given length and given
+ allowed_chars. Note that the default value of allowed_chars does not
+ have "I" or "O" or letters and digits that look similar -- just to
+ avoid confusion.
+ """
+ return get_random_string(length, allowed_chars)
+
+ def get_by_natural_key(self, username):
+ return self.get(**{getattr(self.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.
"""
@@ -158,35 +180,20 @@ def create_user(self, username, email=None, password=None):
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)
+ 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):
- u = self.create_user(username, email, password)
+ 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
- def make_random_password(self, length=10,
- allowed_chars='abcdefghjkmnpqrstuvwxyz'
- 'ABCDEFGHJKLMNPQRSTUVWXYZ'
- '23456789'):
- """
- Generates a random password with the given length and given
- allowed_chars. Note that the default value of allowed_chars does not
- have "I" or "O" or letters and digits that look similar -- just to
- avoid confusion.
- """
- return get_random_string(length, allowed_chars)
-
- def get_by_natural_key(self, username):
- return self.get(username=username)
-
# A few helper functions for common logic between User and AnonymousUser.
def _user_get_all_permissions(user, obj):
@@ -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,21 +227,73 @@ def _user_has_module_perms(user, app_label):
return False
+class AbstractBaseUser(models.Model):
+ password = models.CharField(_('password'), max_length=128)
+ last_login = models.DateTimeField(_('last login'), default=timezone.now)
+
+ REQUIRED_FIELDS = []
+
+ class Meta:
+ abstract = True
+
+ def is_anonymous(self):
+ """
+ Always returns False. This is a way of comparing User objects to
+ anonymous users.
+ """
+ return False
+
+ def is_authenticated(self):
+ """
+ Always return True. This is a way to tell if the user has been
+ authenticated in templates.
+ """
+ return True
+
+ def set_password(self, raw_password):
+ self.password = make_password(raw_password)
+
+ def check_password(self, raw_password):
+ """
+ Returns a boolean of whether the raw_password was correct. Handles
+ hashing formats behind the scenes.
+ """
+ def setter(raw_password):
+ self.set_password(raw_password)
+ self.save(update_fields=["password"])
+ return check_password(raw_password, self.password, setter)
+
+ def set_unusable_password(self):
+ # Sets a value that will never be a valid hash
+ self.password = make_password(None)
+
+ 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 User(models.Model):
+class AbstractUser(AbstractBaseUser):
"""
- Users within the Django authentication system are represented by this
- model.
+ An abstract base class implementing a fully featured User model with
+ admin-compliant permissions.
- Username and password are required. Other fields are optional.
+ 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'))
+ '@/./+/-/_ 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(_('e-mail address'), blank=True)
- password = models.CharField(_('password'), max_length=128)
+ 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.'))
@@ -248,7 +303,6 @@ class User(models.Model):
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 '
@@ -257,11 +311,15 @@ class User(models.Model):
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
@@ -272,20 +330,6 @@ def natural_key(self):
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
- def is_anonymous(self):
- """
- Always returns False. This is a way of comparing User objects to
- anonymous users.
- """
- return False
-
- def is_authenticated(self):
- """
- Always return True. This is a way to tell if the user has been
- authenticated in templates.
- """
- return True
-
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
@@ -293,25 +337,9 @@ def get_full_name(self):
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)
-
- def check_password(self, raw_password):
- """
- Returns a boolean of whether the raw_password was correct. Handles
- hashing formats behind the scenes.
- """
- def setter(raw_password):
- self.set_password(raw_password)
- self.save(update_fields=["password"])
- return check_password(raw_password, self.password, setter)
-
- def set_unusable_password(self):
- # Sets a value that will never be a valid hash
- self.password = make_password(None)
-
- def has_usable_password(self):
- return is_password_usable(self.password)
+ def get_short_name(self):
+ "Returns the short name for the user."
+ return self.first_name
def get_group_permissions(self, obj=None):
"""
@@ -381,6 +409,8 @@ def get_profile(self):
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 @@ def get_profile(self):
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 @@ def __ne__(self, other):
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
37 django/contrib/auth/tests/__init__.py
View
@@ -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 *
Jannis Leidel Owner
jezdez added a note

Nooooooooo!

Russell Keith-Magee Owner

This is one of the few places that I'll defend import * -- and it's because I've been bitten far to many times writing a test case and then having it not be run.

Admittedly, the real fix here is to get the unittest2 test discovery in place so that this sort of init.py isn't required at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+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'
5 django/contrib/auth/tests/auth_backends.py
View
@@ -2,12 +2,14 @@
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 @@ def get_group_permissions(self, user, obj=None):
return ['none']
+@skipIfCustomUser
class RowlevelBackendTest(TestCase):
"""
Tests for auth backend that supports object level permissions
@@ -223,6 +226,7 @@ def test_get_all_permissions(self):
self.assertEqual(self.user1.get_all_permissions(TestObj()), set(['anon']))
+@skipIfCustomUser
@override_settings(AUTHENTICATION_BACKENDS=[])
class NoBackendsTest(TestCase):
"""
@@ -235,6 +239,7 @@ def test_raises_exception(self):
self.assertRaises(ImproperlyConfigured, self.user.has_perm, ('perm', TestObj(),))
+@skipIfCustomUser
class InActiveUserBackendTest(TestCase):
"""
Tests for a inactive user
45 django/contrib/auth/tests/basic.py
View
@@ -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 @@ def test_user(self):
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 @@ def test_createsuperuser_management_command(self):
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 @@ def test_createsuperuser_nolocale(self):
# 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)
-
- except TypeError as e:
+ call_command("createsuperuser",
+ interactive=True,
+ username="nolocale@somewhere.org",
+ email="nolocale@somewhere.org",
+ stdout=new_io
+ )
+
+ except TypeError:
self.fail("createsuperuser fails if the OS provides no information about the current locale")
finally:
@@ -143,3 +153,24 @@ class mock_getpass: pass
# 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()
3  django/contrib/auth/tests/context_processors.py
View
@@ -2,12 +2,13 @@
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'),
75 django/contrib/auth/tests/custom_user.py
View
@@ -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'

USERNAME_FIELD!! Finally! \0/\0/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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
4 django/contrib/auth/tests/decorators.py
View
@@ -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
8 django/contrib/auth/tests/forms.py
View
@@ -4,16 +4,17 @@
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 @@ def test_success(self):
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 @@ def test_success(self):
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 @@ def test_success(self):
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 @@ def test_field_order(self):
['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 @@ def test_bug_17944_unknown_password_algorithm(self):
form.as_table())
+@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class PasswordResetFormTest(TestCase):
100 django/contrib/auth/tests/management.py
View
@@ -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 @@ def test_i18n(self):
self.assertEqual(management.get_default_username(), 'julia')
+@skipIfCustomUser
class ChangepasswordManagementCommandTestCase(TestCase):
def setUp(self):
@@ -48,7 +56,7 @@ def tearDown(self):
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 @@ def test_that_max_tries_exits_1(self):
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)
6 django/contrib/auth/tests/models.py
View
@@ -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 @@ def test_site_profile_not_available(self):
user.get_profile()
+@skipIfCustomUser
@override_settings(USE_TZ=False)
class NaturalKeysTestCase(TestCase):
fixtures = ['authtestdata.json']
@@ -45,6 +48,7 @@ def test_group_natural_key(self):
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 @@ def test_user_is_created_and_added_to_group(self):
self.assertEqual(group, user.groups.get())
+@skipIfCustomUser
@override_settings(USE_TZ=False)
class LoadDataWithNaturalKeysTestCase(TestCase):
fixtures = ['natural.json']
@@ -65,6 +70,7 @@ def test_user_is_created_and_added_to_group(self):
self.assertEqual(group, user.groups.get())
+@skipIfCustomUser
class UserManagerTestCase(TestCase):
def test_create_user(self):
4 django/contrib/auth/tests/remote_user.py
View
@@ -3,10 +3,12 @@
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 @@ def configure_user(self, user):
return user
+@skipIfCustomUser
class RemoteUserCustomTest(RemoteUserTest):
"""
Tests a custom RemoteUserBackend subclass that overrides the clean_username
2  django/contrib/auth/tests/signals.py
View
@@ -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'
2  django/contrib/auth/tests/tokens.py
View
@@ -4,10 +4,12 @@
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):
9 django/contrib/auth/tests/utils.py
View
@@ -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)
30 django/contrib/auth/tests/views.py
View
@@ -16,6 +16,7 @@
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 @@ def assertContainsEscaped(self, response, text, **kwargs):
return self.assertContains(response, escape(force_text(text)), **kwargs)
+@skipIfCustomUser
class AuthViewNamedURLTests(AuthViewsTestCase):
urls = 'django.contrib.auth.urls'
@@ -75,6 +77,7 @@ def test_named_urls(self):
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 @@ def test_confirm_different_passwords(self):
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 @@ def test_password_change_done_fails(self):
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 @@ def test_security_check(self, password='password'):
"%s should be allowed" % good_url)
+@skipIfCustomUser
class LoginURLSettings(AuthViewsTestCase):
def setUp(self):
@@ -347,6 +376,7 @@ def test_remote_login_url_with_next_querystring(self):
querystring.urlencode('/')))
+@skipIfCustomUser
class LogoutTest(AuthViewsTestCase):
def confirm_logged_out(self):
1  django/contrib/auth/tokens.py
View
@@ -4,6 +4,7 @@
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
19 django/contrib/auth/views.py
View
@@ -15,10 +15,9 @@
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',
17 django/contrib/comments/models.py
View
@@ -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 @@ def get_content_object_url(self):
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'),
Ilkka Hakkari
derega added a note

Why this does not use get_user_model()? Is the idea that get_user_model() is used only if contrib.auth is used in a project and the contrib.comments does not want to depend on contrib.auth?

Russell Keith-Magee Owner

get_user_model() returns the fully loaded model, so using it in a model definition would introduce a circular dependency - you need to load all the models before get_user_model() will work correctly, but get_user_model() can't work until all the models are loaded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
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 @@ def _get_userinfo(self):
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 @@ def _set_name(self, val):
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 @@ def _set_email(self, val):
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 @@ def get_as_text(self):
}
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)
13 django/core/exceptions.py
View
@@ -3,42 +3,54 @@
"""
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 @@ def update_error_dict(self, error_dict):
else:
error_dict[NON_FIELD_ERRORS] = self.messages
return error_dict
-
Claude Paroz Collaborator
claudep added a note

When files are completely unrelated to the added feature, PEP8-only changes should possibly be committed separately. For the next time :-)

Russell Keith-Magee Owner

Ah - at one point in the patches history, there was a new exception being raised; I just neglected to back out the PEP8 fixes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
1  django/core/management/commands/sqlall.py
View
@@ -6,6 +6,7 @@
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)."
2  django/core/management/commands/syncdb.py
View
@@ -68,6 +68,7 @@ def handle_noargs(self, **options):
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 @@ def model_installed(model):
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  django/core/management/commands/validate.py
View
@@ -1,5 +1,6 @@
from django.core.management.base import NoArgsCommand
+
class Command(NoArgsCommand):
help = "Validates all installed models."