Version 1 (modified by 13 years ago) ( diff ) | ,
---|
Improving contrib.auth
Django's auth application has been largely unchanged since the initial release of Django. Unfortunately, the User model enforces a number of design decisions that have proven problematic over time. These include (but are not limited to):
- Username is required, and limited to 30 characters.
- Email has a 75 character limit
- Email isn't unique or required.
- There is a "first name" and "last name" field, which is a western-centric organisation of naming and doesn't work well in other cultures.
- There's no ability to add additional fields to the base user model.
The last point requires some elaboration. The overwhelming stated use case for adding additional fields is to avoid the need for a UserProfile-style 1-1 model on aesthetic or performance grounds. The Django core team has historically counselled users away from this technique.
However, there is also a use case for adding non-email, non-username identification marks to the base User object (e.g., an authentication identifier from another login system). This requires additional fields, and needing to perform a join to simply validate a user is wasteful.
Therefore, there has been a long-lived request to "fix" auth.User, either by removing the base limitations of the model, or making the User model user-configurable in some way.
Some django-developer discussions that cover this topic (there are many, over many years):
- http://groups.google.com/group/django-developers/browse_thread/thread/6dadb540ca5f7d9b
- http://groups.google.com/group/django-developers/browse_thread/thread/61c15301a89d88be
Solution 1: Superminimal update
Don't make any significant contribution to contrib.auth
-- just make 2 changes to he User model, phased in over several release cycles.
- increase the length of the username field to 254 characters, so it can hold an email if necessary
- Make the email field 254 characters long.
Implementation
- Introduce a new
USE_NEW_USER_SETTINGS
setting. This would be set as False by default in global_settings.py, and True by default in the project template. This means existing projects get a value of False, but can opt-in to True at their convenience; new projects get a value of True. - Modify the existing User model to use this setting to determine the length/uniqueness
- Introduce a "MigrationWarning" that will be raised if USE_NEW_USER_SETTINGS is False. Assuming this is introduced in 1.5, Django 1.5 users would get the warning if they have USE_NEW_USER_SETTINGS set to False. Django 1.6 would raise an error if USE_NEW_USER_SETTINGS is False.
Advantages
- Very little code to write -- not much more than a dozen lines.
- Solves the most common request for auth.User -- making it easy to make email addresses the username.
Problems
- Doesn't actually fix the problem for any use case other than "email address as username"
- Introduces a setting that immediately becomes deprecated (since it won't be needed once the migration is complete)
- Doesn't address the problem with any other usage of EmailField having a max_length of 75.
- Introduces a circular dependency between settings and models. When settings are loaded, INSTALLED_APPS is inspected, and each models file is loaded. If a models file contains a reference to settings, hilarity can ensue. This isn't a problem *most* of the time, but it can lead to some interesting side effects.
Solution 1a: Superminimal with forced migration
As for Solution 1, but don't have the setting, and *require* the migration as part of the 1.5 upgrade path.
Advantages
As for Solution 1, plus:
- Guarantees that every Django 1.5 user has the same user model
Problems
- Is a huge backwards incompatibility: Requires that every Django user upgrading read, understand, and act upon the instructions in the release notes.
- Poor failure modes. If users fail to act on the release notes, their projects won't fail until the first user enters an email that is longer than 75 characters, or a username longer than 30 characters, at which point DatabaseErrors will be raised.
Solution 2: USER_MODEL setting
- Allow users to specify a User model via a setting.
- This is essentially the original #3011 proposal.
Advantages
- Allows any user model, providing it adheres to some basic contract (defined by Django, probably using contrib.admin as a baseline use case)
- Mirrors current usage in contrib.comments
Optionally:
- Introduce an AbstractUser base class that implements the core of the User contract, and encourage people developing User models to extend this base class.
- Modify auth.User to extend the new AbstractUser. Care must be taken to ensure that the database expression of the new User model is identical to the old User model, to ensure backwards compatibility.
- Introduce other concrete User models implementing common login patterns (e.g., login by email)
In order to support contrib.admin, there will be a need to include some permissions API in the User model -- this is required by the admin app, but doesn't necessarily have to be part of the AbstractUser. Care should be taken to ensure that the AbstractUser really does represent the minimal requirements for user authentication, and that authorisation concerns are kept separate.
All three points here are optional. We can introduce an AbstractUser without changing the base User. We also don't have to introduce other concrete models -- we could leave this up to the community at large to develop an ecosystem of User models.
Problems
- Has the same settings-models circular dependency problem as Solution 1.
- Doesn't address the EmailField length problem for existing users. We could address this by having a User model (reflecting current field lengths) and a new SimpleUser (that reflects better defaults); then use global_settings and project template settings to define which User is the default for new vs existing projects.
- Doesn't solve the analogous problem for any other project. E.g., contrib.comments already has pluggable Comments models, and has invented a bespoke solution. Other projects will have similar needs; this solution doesn't address the duplication of code.
Solution 3: Leverage App Refactor
- Land Arthur Koziel's App Refactor patch from GSoC 2010. This introduces a number of benefits --reliable hooks for app startup, configurable app labels, predictable module loading, amongst others -- but the one that matters for the purposes of auth.User is that it allows Apps to be treated as items that need to be configured as a runtime activity. In this case, we need to be able to specify, at a project level, which model is your "User" model in the auth app.
- Introduce the concept of a LazyForeignKey. LazyForeignKey is a normal foreign key, with all the usual foreign key behaviors; the only difference is that the model it links to isn't specified in the model -- it's a configuration item drawn from an application configuration. So, ForeignKey('auth.User') creates a foreign key to django.contrib.auth.User; LazyForeignKey('auth.User') asks the auth app for the model that is being used as the 'User' model, and creates a foreign key to that. This should just be a matter of slotting into the existing model reference resolution code, which is something that the app refactor cleans up.
- Add a Meta option to models --
plugable
-- which controls whether the model can be replaced at runtime. If it can be, then the model may not be synchronised (so we don't get empty auth_user tables).
Optionally:
- Introduce an AbstractUser, and other concrete User models, same as for Solution 2
Advantages
- Doesn't have the circular dependency between settings and User
- Solves the generic problem. contrib.comments could be retrofitted to use this approach, and any other application could do the same.
Problems
- No transparent update path -- requires that third party apps be updated to be "pluggable app compatible". This means app authors need to make all ForeignKey(User) into LazyForeignKey('auth.User'), and modify any usage of forms/fields etc. This could be considered a benefit, however; Migrating User models is a nontrivial step, and it would
- Doesn't address the immediate problem for EmailField. We could do the same User/SimpleUser conversion here; with the added benefit that we are also introducing apprefactor, so we can use the distinction between an "unconfigured" auth app and a "Django 1.5 AppRefactor Configured" auth app as the point for identifying whether User or SimpleUser is in use.
Solution 4: contrib.newauth
- Deprecate contrib.auth, and create a new contrib.newauth in the same way we deprecated forms into newforms.
Ian Lewis has a project that starts down this path (See https://bitbucket.org/IanLewis/django-newauth/); however:
- it doesn't yet handle permissions, so it can't be used for contrib.admin
- It exhibits the settings-models dependency problem
- It introduces a new feature -- the ability to have *multiple* user models -- that isn't an obvious improvement. Additional discussion is required before being adopted.
Problems
- There's no pret-a-porter project ready as a candidate. Rebuilding contrib.auth is a big undertaking, and doesn't yet have a clear design (or even design requirements).
Parallel concerns
Most of these solutions don't fully address the limitations with EmailField. As a separate concern, we could address this problem in the same way that Solution 1 proposes to fix the User model, but focussed on the EmailField's max_length argument specifically:
- Introduce a new
ALLOW_RFC_COMPLIANT_EMAIL_ADDRESSES
setting. This would be set as False by default in global_settings.py, and True by default in the project template. This means existing projects get a value of False, but can opt-in to True at their convenience; new projects get a value of True. - Modify the EmailField definition to use this setting to determine the length/uniqueness
- Introduce a "MigrationWarning" that will be raised if
ALLOW_RFC_COMPLIANT_EMAIL_ADDRESSES
is False (or max_length isn't manually specified). Assuming this is introduced in 1.5, Django 1.5 users would get the warning if they have ALLOW_RFC_COMPLIANT_EMAIL_ADDRESSES set to False. Django 1.6 would raise an error if ALLOW_RFC_COMPLIANT_EMAIL_ADDRESSES is False.
This would address *all* EmailFields, not just the one in auth.User. This fix could be used in conjunction with any of the solutions proposed here; in fact, it would make some of them simpler (since there wouldn't be a need for a SimpleUser to migrate to an improved email field length).
Recommendations
Discussion on django-developers suggests that complete consensus is unlikely; Currently awaiting BDFL mandate.