New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom user models (#3011) #370
Changes from 34 commits
7cc0baf
dabe362
507bb50
8e3fd70
e29c010
7e82e83
d088b3a
75118bd
e6aaf65
40ea8b8
20d1892
2c5e833
1952656
f2ec915
57ac6e3
5d7bb22
334cdfc
9184972
579f152
d9f5e5a
08bcb4a
b441a6b
52a02f1
b550a6d
fd8bb4e
a9491a8
abcb027
dfd7213
dbb3900
280bf19
913e1ac
ffd535e
5a04cde
6494bf9
0229209
98aba85
e2b6e22
8a527dd
29d1abb
531e771
d84749a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,19 @@ 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'") | ||
return get_model(app_label, model_name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if settings.AUTH_USER_MODEL is set to an invalid value that conforms to the pattern ie: "typodapp.nonmodel" then get_model and hence get_user_model will return None. I'd propose that get_user_model test for None and raise ImproperlyConfigured There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point - I've just implemented this. |
||
|
||
|
||
def get_user(request): | ||
from django.contrib.auth.models import AnonymousUser | ||
try: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rofl, shouldn't we just change get_field to get_field_by_name('bla')[0] then? Using ugly API to be more performent smells bad if you ask me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hell yeah - fix the cause, not the symptom. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turns out it's not quite as simple as I thought... Firstly - Yes, it's O(n), but, N is very small, and the way get_field_by_name() is O(1) is by pre-caching all possible lookups, so there's a first-call cache warming expense. In this usage, we're on a management command, and get_field will be invoked exactly once, so we're going to be in territory where the expense of cache warming to get the O(1) lookup will exceed the O(N) cost we're trying to avoid. Secondly, it's not as simple as replacing the internals of get_field() with get_field_by_name(), because the cache priming in get_field_by_name() assumes all the models have been loaded (because it includes all reverse FK and M2M relations). get_field() only depends on self. This should get a little cleaner once app loading lands, but as it currently stands, it's difficult to guarantee that get_field_by_name() is actually callable. So - I'm going to call this a no-fix; or, at least, a "not my bailiwick" fix. Meta certainly could be cleaned up, but that's a much bigger fish that needs frying. |
||
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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it not be more future proof to have the FK set to get_user_model() ?
The disadvantage is it is somewhat less direct - but would allow more flexibility in future refactors of how the swapped model might be specified (hint hint, wink wink app-loading ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is deliberate -- otherwise we have a 'chicken and egg' situation. In order for get_user_model() to run, you need to have the app cache parsed; but the app cache can't be fully populated until all the models are loaded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yes - I forgot just how much I had changed to actually cook that particular chicken and scramble that particular egg in the app-loading branch. Looks like promoting the convention of explicitly referring to the setting is unavoidable for now then.